changeset 2382:7284093111b0

big reorganization to cleanly separate framework vs. server
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 29 Aug 2017 21:17:35 +0200
parents b8969010b534
children 741bb76634d3
files CMakeLists.txt Core/DicomNetworking/DicomFindAnswers.cpp Core/DicomNetworking/DicomFindAnswers.h Core/DicomNetworking/DicomServer.cpp Core/DicomNetworking/DicomServer.h Core/DicomNetworking/DicomUserConnection.cpp Core/DicomNetworking/DicomUserConnection.h Core/DicomNetworking/IApplicationEntityFilter.h Core/DicomNetworking/IFindRequestHandler.h Core/DicomNetworking/IFindRequestHandlerFactory.h Core/DicomNetworking/IMoveRequestHandler.h Core/DicomNetworking/IMoveRequestHandlerFactory.h Core/DicomNetworking/IStoreRequestHandler.h Core/DicomNetworking/IStoreRequestHandlerFactory.h Core/DicomNetworking/IWorklistRequestHandler.h Core/DicomNetworking/IWorklistRequestHandlerFactory.h Core/DicomNetworking/Internals/CommandDispatcher.cpp Core/DicomNetworking/Internals/CommandDispatcher.h Core/DicomNetworking/Internals/FindScp.cpp Core/DicomNetworking/Internals/FindScp.h Core/DicomNetworking/Internals/MoveScp.cpp Core/DicomNetworking/Internals/MoveScp.h Core/DicomNetworking/Internals/StoreScp.cpp Core/DicomNetworking/Internals/StoreScp.h Core/DicomNetworking/RemoteModalityParameters.cpp Core/DicomNetworking/RemoteModalityParameters.h Core/DicomNetworking/ReusableDicomUserConnection.cpp Core/DicomNetworking/ReusableDicomUserConnection.h Core/DicomParsing/DicomDirWriter.cpp Core/DicomParsing/DicomDirWriter.h Core/DicomParsing/DicomModification.cpp Core/DicomParsing/DicomModification.h Core/DicomParsing/FromDcmtkBridge.cpp Core/DicomParsing/FromDcmtkBridge.h Core/DicomParsing/Internals/DicomFrameIndex.cpp Core/DicomParsing/Internals/DicomFrameIndex.h Core/DicomParsing/Internals/DicomImageDecoder.cpp Core/DicomParsing/Internals/DicomImageDecoder.h Core/DicomParsing/ParsedDicomFile.cpp Core/DicomParsing/ParsedDicomFile.h Core/DicomParsing/ToDcmtkBridge.cpp Core/DicomParsing/ToDcmtkBridge.h Core/Enumerations.cpp Core/Enumerations.h Core/PrecompiledHeaders.h OrthancServer/DefaultDicomImageDecoder.h OrthancServer/DicomDirWriter.cpp OrthancServer/DicomDirWriter.h OrthancServer/DicomInstanceToStore.cpp OrthancServer/DicomInstanceToStore.h OrthancServer/DicomModification.cpp OrthancServer/DicomModification.h OrthancServer/DicomProtocol/DicomFindAnswers.cpp OrthancServer/DicomProtocol/DicomFindAnswers.h OrthancServer/DicomProtocol/DicomServer.cpp OrthancServer/DicomProtocol/DicomServer.h OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/DicomProtocol/DicomUserConnection.h OrthancServer/DicomProtocol/IApplicationEntityFilter.h OrthancServer/DicomProtocol/IFindRequestHandler.h OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h OrthancServer/DicomProtocol/IMoveRequestHandler.h OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h OrthancServer/DicomProtocol/IStoreRequestHandler.h OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h OrthancServer/DicomProtocol/IWorklistRequestHandler.h OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h OrthancServer/DicomProtocol/RemoteModalityParameters.cpp OrthancServer/DicomProtocol/RemoteModalityParameters.h OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp OrthancServer/DicomProtocol/ReusableDicomUserConnection.h OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/Internals/CommandDispatcher.cpp OrthancServer/Internals/CommandDispatcher.h OrthancServer/Internals/DicomFrameIndex.cpp OrthancServer/Internals/DicomFrameIndex.h OrthancServer/Internals/DicomImageDecoder.cpp OrthancServer/Internals/DicomImageDecoder.h OrthancServer/Internals/FindScp.cpp OrthancServer/Internals/FindScp.h OrthancServer/Internals/MoveScp.cpp OrthancServer/Internals/MoveScp.h OrthancServer/Internals/StoreScp.cpp OrthancServer/Internals/StoreScp.h OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancFindRequestHandler.h OrthancServer/OrthancInitialization.cpp OrthancServer/OrthancInitialization.h OrthancServer/OrthancMoveRequestHandler.cpp OrthancServer/OrthancMoveRequestHandler.h OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/OrthancRestApi/OrthancRestApi.cpp OrthancServer/OrthancRestApi/OrthancRestApi.h OrthancServer/OrthancRestApi/OrthancRestArchive.cpp OrthancServer/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h OrthancServer/PrecompiledHeadersServer.h OrthancServer/QueryRetrieveHandler.cpp OrthancServer/Scheduler/ModifyInstanceCommand.h OrthancServer/Search/HierarchicalMatcher.cpp OrthancServer/Search/HierarchicalMatcher.h OrthancServer/Search/IFindConstraint.cpp OrthancServer/Search/LookupIdentifierQuery.cpp OrthancServer/Search/LookupResource.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/ServerEnumerations.cpp OrthancServer/ServerEnumerations.h OrthancServer/ServerIndex.cpp OrthancServer/ToDcmtkBridge.cpp OrthancServer/ToDcmtkBridge.h OrthancServer/main.cpp Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h UnitTestsSources/DicomMapTests.cpp UnitTestsSources/FromDcmtkTests.cpp UnitTestsSources/JpegLosslessTests.cpp UnitTestsSources/MultiThreadingTests.cpp
diffstat 122 files changed, 13238 insertions(+), 13234 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Aug 29 19:59:01 2017 +0200
+++ b/CMakeLists.txt	Tue Aug 29 21:17:35 2017 +0200
@@ -166,23 +166,27 @@
 
 
 set(ORTHANC_SERVER_SOURCES
+  Core/DicomParsing/DicomDirWriter.cpp
+  Core/DicomParsing/DicomModification.cpp
+  Core/DicomParsing/FromDcmtkBridge.cpp
+  Core/DicomParsing/Internals/DicomFrameIndex.cpp
+  Core/DicomParsing/Internals/DicomImageDecoder.cpp
+  Core/DicomParsing/ParsedDicomFile.cpp
+  Core/DicomParsing/ToDcmtkBridge.cpp
+
+  Core/DicomNetworking/DicomFindAnswers.cpp
+  Core/DicomNetworking/DicomServer.cpp
+  Core/DicomNetworking/DicomUserConnection.cpp
+  Core/DicomNetworking/RemoteModalityParameters.cpp
+  Core/DicomNetworking/ReusableDicomUserConnection.cpp
+  Core/DicomNetworking/Internals/CommandDispatcher.cpp
+  Core/DicomNetworking/Internals/FindScp.cpp
+  Core/DicomNetworking/Internals/MoveScp.cpp
+  Core/DicomNetworking/Internals/StoreScp.cpp
+
   OrthancServer/DatabaseWrapper.cpp
   OrthancServer/DatabaseWrapperBase.cpp
-  OrthancServer/DicomDirWriter.cpp
-  OrthancServer/DicomModification.cpp
-  OrthancServer/DicomProtocol/DicomFindAnswers.cpp
-  OrthancServer/DicomProtocol/DicomServer.cpp
-  OrthancServer/DicomProtocol/DicomUserConnection.cpp
-  OrthancServer/DicomProtocol/RemoteModalityParameters.cpp
-  OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp
   OrthancServer/ExportedResource.cpp
-  OrthancServer/FromDcmtkBridge.cpp
-  OrthancServer/Internals/CommandDispatcher.cpp
-  OrthancServer/Internals/DicomFrameIndex.cpp
-  OrthancServer/Internals/DicomImageDecoder.cpp
-  OrthancServer/Internals/FindScp.cpp
-  OrthancServer/Internals/MoveScp.cpp
-  OrthancServer/Internals/StoreScp.cpp
   OrthancServer/LuaScripting.cpp
   OrthancServer/OrthancFindRequestHandler.cpp
   OrthancServer/OrthancHttpHandler.cpp
@@ -195,7 +199,6 @@
   OrthancServer/OrthancRestApi/OrthancRestModalities.cpp
   OrthancServer/OrthancRestApi/OrthancRestResources.cpp
   OrthancServer/OrthancRestApi/OrthancRestSystem.cpp
-  OrthancServer/ParsedDicomFile.cpp
   OrthancServer/QueryRetrieveHandler.cpp
   OrthancServer/Search/HierarchicalMatcher.cpp
   OrthancServer/Search/IFindConstraint.cpp
@@ -211,7 +214,6 @@
   OrthancServer/ServerIndex.cpp
   OrthancServer/ServerToolbox.cpp
   OrthancServer/SliceOrdering.cpp
-  OrthancServer/ToDcmtkBridge.cpp
 
   # From "lua-scripting" branch
   OrthancServer/DicomInstanceToStore.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/DicomFindAnswers.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,178 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomFindAnswers.h"
+
+#include "../DicomParsing/FromDcmtkBridge.h"
+#include "../OrthancException.h"
+
+#include <memory>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <boost/noncopyable.hpp>
+
+
+namespace Orthanc
+{
+  void DicomFindAnswers::AddAnswerInternal(ParsedDicomFile* answer)
+  {
+    std::auto_ptr<ParsedDicomFile> protection(answer);
+
+    if (isWorklist_)
+    {
+      // These lines are necessary when serving worklists, otherwise
+      // Orthanc does not behave as "wlmscpfs"
+      protection->Remove(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID);
+      protection->Remove(DICOM_TAG_SOP_INSTANCE_UID);
+    }
+
+    protection->ChangeEncoding(encoding_);
+
+    answers_.push_back(protection.release());
+  }
+
+
+  DicomFindAnswers::DicomFindAnswers(bool isWorklist) : 
+    encoding_(GetDefaultDicomEncoding()),
+    isWorklist_(isWorklist),
+    complete_(true)
+  {
+  }
+
+
+  void DicomFindAnswers::SetEncoding(Encoding encoding)
+  {
+    for (size_t i = 0; i < answers_.size(); i++)
+    {
+      assert(answers_[i] != NULL);
+      answers_[i]->ChangeEncoding(encoding);
+    }
+
+    encoding_ = encoding;
+  }
+
+
+  void DicomFindAnswers::SetWorklist(bool isWorklist)
+  {
+    if (answers_.empty())
+    {
+      isWorklist_ = isWorklist;
+    }
+    else
+    {
+      // This set of answers is not empty anymore, cannot change its type
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  void DicomFindAnswers::Clear()
+  {
+    for (size_t i = 0; i < answers_.size(); i++)
+    {
+      assert(answers_[i] != NULL);
+      delete answers_[i];
+    }
+
+    answers_.clear();
+  }
+
+
+  void DicomFindAnswers::Reserve(size_t size)
+  {
+    if (size > answers_.size())
+    {
+      answers_.reserve(size);
+    }
+  }
+
+
+  void DicomFindAnswers::Add(const DicomMap& map)
+  {
+    AddAnswerInternal(new ParsedDicomFile(map, encoding_));
+  }
+
+
+  void DicomFindAnswers::Add(ParsedDicomFile& dicom)
+  {
+    AddAnswerInternal(dicom.Clone());
+  }
+
+  void DicomFindAnswers::Add(const void* dicom,
+                             size_t size)
+  {
+    AddAnswerInternal(new ParsedDicomFile(dicom, size));
+  }
+
+
+  ParsedDicomFile& DicomFindAnswers::GetAnswer(size_t index) const
+  {
+    if (index < answers_.size())
+    {
+      return *answers_[index];
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  DcmDataset* DicomFindAnswers::ExtractDcmDataset(size_t index) const
+  {
+    return new DcmDataset(*GetAnswer(index).GetDcmtkObject().getDataset());
+  }
+
+
+  void DicomFindAnswers::ToJson(Json::Value& target,
+                                size_t index,
+                                bool simplify) const
+  {
+    DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Human : DicomToJsonFormat_Full);
+    GetAnswer(index).DatasetToJson(target, format, DicomToJsonFlags_None, 0);
+  }
+
+
+  void DicomFindAnswers::ToJson(Json::Value& target,
+                                bool simplify) const
+  {
+    target = Json::arrayValue;
+
+    for (size_t i = 0; i < GetSize(); i++)
+    {
+      Json::Value answer;
+      ToJson(answer, i, simplify);
+      target.append(answer);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/DicomFindAnswers.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,109 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../DicomParsing/ParsedDicomFile.h"
+
+namespace Orthanc
+{
+  class DicomFindAnswers : public boost::noncopyable
+  {
+  private:
+    Encoding                      encoding_;
+    bool                          isWorklist_;
+    std::vector<ParsedDicomFile*> answers_;
+    bool                          complete_;
+
+    void AddAnswerInternal(ParsedDicomFile* answer);
+
+  public:
+    DicomFindAnswers(bool isWorklist);
+
+    ~DicomFindAnswers()
+    {
+      Clear();
+    }
+
+    Encoding GetEncoding() const
+    {
+      return encoding_;
+    }
+
+    void SetEncoding(Encoding encoding);
+
+    void SetWorklist(bool isWorklist);
+
+    bool IsWorklist() const
+    {
+      return isWorklist_;
+    }
+
+    void Clear();
+
+    void Reserve(size_t index);
+
+    void Add(const DicomMap& map);
+
+    void Add(ParsedDicomFile& dicom);
+
+    void Add(const void* dicom,
+             size_t size);
+
+    size_t GetSize() const
+    {
+      return answers_.size();
+    }
+
+    ParsedDicomFile& GetAnswer(size_t index) const;
+
+    DcmDataset* ExtractDcmDataset(size_t index) const;
+
+    void ToJson(Json::Value& target,
+                bool simplify) const;
+
+    void ToJson(Json::Value& target,
+                size_t index,
+                bool simplify) const;
+
+    bool IsComplete() const
+    {
+      return complete_;
+    }
+
+    void SetComplete(bool isComplete)
+    {
+      complete_ = isComplete;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/DicomServer.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,382 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomServer.h"
+
+#include "../../Core/Logging.h"
+#include "../../Core/MultiThreading/RunnableWorkersPool.h"
+#include "../../Core/OrthancException.h"
+#include "../../Core/Toolbox.h"
+#include "Internals/CommandDispatcher.h"
+
+#include <boost/thread.hpp>
+
+#if defined(__linux__)
+#include <cstdlib>
+#endif
+
+
+namespace Orthanc
+{
+  struct DicomServer::PImpl
+  {
+    boost::thread  thread_;
+    T_ASC_Network *network_;
+    std::auto_ptr<RunnableWorkersPool>  workers_;
+  };
+
+
+  void DicomServer::ServerThread(DicomServer* server)
+  {
+    LOG(INFO) << "DICOM server started";
+
+    while (server->continue_)
+    {
+      /* receive an association and acknowledge or reject it. If the association was */
+      /* acknowledged, offer corresponding services and invoke one or more if required. */
+      std::auto_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_));
+
+      try
+      {
+        if (dispatcher.get() != NULL)
+        {
+          server->pimpl_->workers_->Add(dispatcher.release());
+        }
+      }
+      catch (OrthancException& e)
+      {
+        LOG(ERROR) << "Exception in the DICOM server thread: " << e.What();
+      }
+    }
+
+    LOG(INFO) << "DICOM server stopping";
+  }
+
+
+  DicomServer::DicomServer() : 
+    pimpl_(new PImpl),
+    aet_("ANY-SCP")
+  {
+    port_ = 104;
+    modalities_ = NULL;
+    findRequestHandlerFactory_ = NULL;
+    moveRequestHandlerFactory_ = NULL;
+    storeRequestHandlerFactory_ = NULL;
+    worklistRequestHandlerFactory_ = NULL;
+    applicationEntityFilter_ = NULL;
+    checkCalledAet_ = true;
+    associationTimeout_ = 30;
+    continue_ = false;
+  }
+
+  DicomServer::~DicomServer()
+  {
+    if (continue_)
+    {
+      LOG(ERROR) << "INTERNAL ERROR: DicomServer::Stop() should be invoked manually to avoid mess in the destruction order!";
+      Stop();
+    }
+  }
+
+  void DicomServer::SetPortNumber(uint16_t port)
+  {
+    Stop();
+    port_ = port;
+  }
+
+  uint16_t DicomServer::GetPortNumber() const
+  {
+    return port_;
+  }
+
+  void DicomServer::SetAssociationTimeout(uint32_t seconds)
+  {
+    LOG(INFO) << "Setting timeout for DICOM connections if Orthanc acts as SCP (server): " 
+              << seconds << " seconds (0 = no timeout)";
+
+    Stop();
+    associationTimeout_ = seconds;
+  }
+
+  uint32_t DicomServer::GetAssociationTimeout() const
+  {
+    return associationTimeout_;
+  }
+
+
+  void DicomServer::SetCalledApplicationEntityTitleCheck(bool check)
+  {
+    Stop();
+    checkCalledAet_ = check;
+  }
+
+  bool DicomServer::HasCalledApplicationEntityTitleCheck() const
+  {
+    return checkCalledAet_;
+  }
+
+  void DicomServer::SetApplicationEntityTitle(const std::string& aet)
+  {
+    if (aet.size() == 0)
+    {
+      throw OrthancException(ErrorCode_BadApplicationEntityTitle);
+    }
+
+    if (aet.size() > 16)
+    {
+      throw OrthancException(ErrorCode_BadApplicationEntityTitle);
+    }
+
+    for (size_t i = 0; i < aet.size(); i++)
+    {
+      if (!(aet[i] == '-' ||
+            aet[i] == '_' ||
+            isdigit(aet[i]) ||
+            (aet[i] >= 'A' && aet[i] <= 'Z')))
+      {
+        LOG(WARNING) << "For best interoperability, only upper case, alphanumeric characters should be present in AET: \"" << aet << "\"";
+        break;
+      }
+    }
+
+    Stop();
+    aet_ = aet;
+  }
+
+  const std::string& DicomServer::GetApplicationEntityTitle() const
+  {
+    return aet_;
+  }
+
+  void DicomServer::SetRemoteModalities(IRemoteModalities& modalities)
+  {
+    Stop();
+    modalities_ = &modalities;
+  }
+  
+  DicomServer::IRemoteModalities& DicomServer::GetRemoteModalities() const
+  {
+    if (modalities_ == NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return *modalities_;
+    }
+  }
+    
+  void DicomServer::SetFindRequestHandlerFactory(IFindRequestHandlerFactory& factory)
+  {
+    Stop();
+    findRequestHandlerFactory_ = &factory;
+  }
+
+  bool DicomServer::HasFindRequestHandlerFactory() const
+  {
+    return (findRequestHandlerFactory_ != NULL);
+  }
+
+  IFindRequestHandlerFactory& DicomServer::GetFindRequestHandlerFactory() const
+  {
+    if (HasFindRequestHandlerFactory())
+    {
+      return *findRequestHandlerFactory_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NoCFindHandler);
+    }
+  }
+
+  void DicomServer::SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& factory)
+  {
+    Stop();
+    moveRequestHandlerFactory_ = &factory;
+  }
+
+  bool DicomServer::HasMoveRequestHandlerFactory() const
+  {
+    return (moveRequestHandlerFactory_ != NULL);
+  }
+
+  IMoveRequestHandlerFactory& DicomServer::GetMoveRequestHandlerFactory() const
+  {
+    if (HasMoveRequestHandlerFactory())
+    {
+      return *moveRequestHandlerFactory_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NoCMoveHandler);
+    }
+  }
+
+  void DicomServer::SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& factory)
+  {
+    Stop();
+    storeRequestHandlerFactory_ = &factory;
+  }
+
+  bool DicomServer::HasStoreRequestHandlerFactory() const
+  {
+    return (storeRequestHandlerFactory_ != NULL);
+  }
+
+  IStoreRequestHandlerFactory& DicomServer::GetStoreRequestHandlerFactory() const
+  {
+    if (HasStoreRequestHandlerFactory())
+    {
+      return *storeRequestHandlerFactory_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NoCStoreHandler);
+    }
+  }
+
+  void DicomServer::SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& factory)
+  {
+    Stop();
+    worklistRequestHandlerFactory_ = &factory;
+  }
+
+  bool DicomServer::HasWorklistRequestHandlerFactory() const
+  {
+    return (worklistRequestHandlerFactory_ != NULL);
+  }
+
+  IWorklistRequestHandlerFactory& DicomServer::GetWorklistRequestHandlerFactory() const
+  {
+    if (HasWorklistRequestHandlerFactory())
+    {
+      return *worklistRequestHandlerFactory_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NoWorklistHandler);
+    }
+  }
+
+  void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory)
+  {
+    Stop();
+    applicationEntityFilter_ = &factory;
+  }
+
+  bool DicomServer::HasApplicationEntityFilter() const
+  {
+    return (applicationEntityFilter_ != NULL);
+  }
+
+  IApplicationEntityFilter& DicomServer::GetApplicationEntityFilter() const
+  {
+    if (HasApplicationEntityFilter())
+    {
+      return *applicationEntityFilter_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NoApplicationEntityFilter);
+    }
+  }
+
+  void DicomServer::Start()
+  {
+    if (modalities_ == NULL)
+    {
+      LOG(ERROR) << "No list of modalities was provided to the DICOM server";
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    
+    Stop();
+
+    /* initialize network, i.e. create an instance of T_ASC_Network*. */
+    OFCondition cond = ASC_initializeNetwork
+      (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_);
+    if (cond.bad())
+    {
+      LOG(ERROR) << "cannot create network: " << cond.text();
+      throw OrthancException(ErrorCode_DicomPortInUse);
+    }
+
+    continue_ = true;
+    pimpl_->workers_.reset(new RunnableWorkersPool(4));   // Use 4 workers - TODO as a parameter?
+    pimpl_->thread_ = boost::thread(ServerThread, this);
+  }
+
+
+  void DicomServer::Stop()
+  {
+    if (continue_)
+    {
+      continue_ = false;
+
+      if (pimpl_->thread_.joinable())
+      {
+        pimpl_->thread_.join();
+      }
+
+      pimpl_->workers_.reset(NULL);
+
+      /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
+      /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
+      OFCondition cond = ASC_dropNetwork(&pimpl_->network_);
+      if (cond.bad())
+      {
+        LOG(ERROR) << "Error while dropping the network: " << cond.text();
+      }
+    }
+  }
+
+
+  bool DicomServer::IsMyAETitle(const std::string& aet) const
+  {
+    if (modalities_ == NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    
+    if (!HasCalledApplicationEntityTitleCheck())
+    {
+      // OK, no check on the AET.
+      return true;
+    }
+    else
+    {
+      return modalities_->IsSameAETitle(aet, GetApplicationEntityTitle());
+    }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/DicomServer.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,136 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1
+#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1
+#endif
+
+#include "IFindRequestHandlerFactory.h"
+#include "IMoveRequestHandlerFactory.h"
+#include "IStoreRequestHandlerFactory.h"
+#include "IWorklistRequestHandlerFactory.h"
+#include "IApplicationEntityFilter.h"
+#include "RemoteModalityParameters.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+
+namespace Orthanc
+{
+  class DicomServer : public boost::noncopyable
+  {
+  public:
+    // WARNING: The methods of this class must be thread-safe
+    class IRemoteModalities : public boost::noncopyable
+    {
+    public:
+      virtual ~IRemoteModalities()
+      {
+      }
+      
+      virtual bool IsSameAETitle(const std::string& aet1,
+                                 const std::string& aet2) = 0;
+
+      virtual bool LookupAETitle(RemoteModalityParameters& modality,
+                                 const std::string& aet) = 0;
+    };
+    
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    bool checkCalledAet_;
+    std::string aet_;
+    uint16_t port_;
+    bool continue_;
+    uint32_t associationTimeout_;
+    IRemoteModalities* modalities_;
+    IFindRequestHandlerFactory* findRequestHandlerFactory_;
+    IMoveRequestHandlerFactory* moveRequestHandlerFactory_;
+    IStoreRequestHandlerFactory* storeRequestHandlerFactory_;
+    IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_;
+    IApplicationEntityFilter* applicationEntityFilter_;
+
+    static void ServerThread(DicomServer* server);
+
+  public:
+    DicomServer();
+
+    ~DicomServer();
+
+    void SetPortNumber(uint16_t port);
+    uint16_t GetPortNumber() const;
+
+    void SetAssociationTimeout(uint32_t seconds);
+    uint32_t GetAssociationTimeout() const;
+
+    void SetCalledApplicationEntityTitleCheck(bool check);
+    bool HasCalledApplicationEntityTitleCheck() const;
+
+    void SetApplicationEntityTitle(const std::string& aet);
+    const std::string& GetApplicationEntityTitle() const;
+
+    void SetRemoteModalities(IRemoteModalities& modalities);
+    IRemoteModalities& GetRemoteModalities() const;
+    
+    void SetFindRequestHandlerFactory(IFindRequestHandlerFactory& handler);
+    bool HasFindRequestHandlerFactory() const;
+    IFindRequestHandlerFactory& GetFindRequestHandlerFactory() const;
+
+    void SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& handler);
+    bool HasMoveRequestHandlerFactory() const;
+    IMoveRequestHandlerFactory& GetMoveRequestHandlerFactory() const;
+
+    void SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& handler);
+    bool HasStoreRequestHandlerFactory() const;
+    IStoreRequestHandlerFactory& GetStoreRequestHandlerFactory() const;
+
+    void SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& handler);
+    bool HasWorklistRequestHandlerFactory() const;
+    IWorklistRequestHandlerFactory& GetWorklistRequestHandlerFactory() const;
+
+    void SetApplicationEntityFilter(IApplicationEntityFilter& handler);
+    bool HasApplicationEntityFilter() const;
+    IApplicationEntityFilter& GetApplicationEntityFilter() const;
+
+    void Start();
+  
+    void Stop();
+
+    bool IsMyAETitle(const std::string& aet) const;
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/DicomUserConnection.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,1220 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomUserConnection.h"
+
+#include "../DicomFormat/DicomArray.h"
+#include "../Logging.h"
+#include "../OrthancException.h"
+#include "../DicomParsing/FromDcmtkBridge.h"
+#include "../DicomParsing/ToDcmtkBridge.h"
+
+#include <dcmtk/dcmdata/dcistrmb.h>
+#include <dcmtk/dcmdata/dcistrmf.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcmetinf.h>
+#include <dcmtk/dcmnet/diutil.h>
+
+#include <set>
+
+
+#ifdef _WIN32
+/**
+ * "The maximum length, in bytes, of the string returned in the buffer 
+ * pointed to by the name parameter is dependent on the namespace provider,
+ * but this string must be 256 bytes or less.
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/ms738527(v=vs.85).aspx
+ **/
+#  define HOST_NAME_MAX 256
+#  include <winsock.h>
+#endif 
+
+
+#if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX)
+/**
+ * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that
+ * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an
+ * array of size _POSIX_HOST_NAME_MAX, invoke gethostname() and expect
+ * that the result will fit."
+ * http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00128.html
+ **/
+#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
+#endif
+
+
+static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax;
+
+/**
+ * "If we have more than 64 storage SOP classes, tools such as
+ * storescu will fail because they attempt to negotiate two
+ * presentation contexts for each SOP class, and there is a total
+ * limit of 128 contexts for one association."
+ **/
+static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64;
+
+
+namespace Orthanc
+{
+  // By default, the timeout for DICOM SCU (client) connections is set to 10 seconds
+  static uint32_t defaultTimeout_ = 10;
+
+  struct DicomUserConnection::PImpl
+  {
+    // Connection state
+    uint32_t dimseTimeout_;
+    uint32_t acseTimeout_;
+    T_ASC_Network* net_;
+    T_ASC_Parameters* params_;
+    T_ASC_Association* assoc_;
+
+    bool IsOpen() const
+    {
+      return assoc_ != NULL;
+    }
+
+    void CheckIsOpen() const;
+
+    void Store(DcmInputStream& is, 
+               DicomUserConnection& connection,
+               const std::string& moveOriginatorAET,
+               uint16_t moveOriginatorID);
+  };
+
+
+  static void Check(const OFCondition& cond)
+  {
+    if (cond.bad())
+    {
+      LOG(ERROR) << "DicomUserConnection: " << std::string(cond.text());
+       throw OrthancException(ErrorCode_NetworkProtocol);
+    }
+  }
+
+  void DicomUserConnection::PImpl::CheckIsOpen() const
+  {
+    if (!IsOpen())
+    {
+      LOG(ERROR) << "DicomUserConnection: First open the connection";
+      throw OrthancException(ErrorCode_NetworkProtocol);
+    }
+  }
+
+
+  void DicomUserConnection::CheckIsOpen() const
+  {
+    pimpl_->CheckIsOpen();
+  }
+
+
+  static void RegisterStorageSOPClass(T_ASC_Parameters* params,
+                                      unsigned int& presentationContextId,
+                                      const std::string& sopClass,
+                                      const char* asPreferred[],
+                                      std::vector<const char*>& asFallback)
+  {
+    Check(ASC_addPresentationContext(params, presentationContextId, 
+                                     sopClass.c_str(), asPreferred, 1));
+    presentationContextId += 2;
+
+    if (asFallback.size() > 0)
+    {
+      Check(ASC_addPresentationContext(params, presentationContextId, 
+                                       sopClass.c_str(), &asFallback[0], asFallback.size()));
+      presentationContextId += 2;
+    }
+  }
+  
+    
+  void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax)
+  {
+    // Flatten an array with the preferred transfer syntax
+    const char* asPreferred[1] = { preferredTransferSyntax.c_str() };
+
+    // Setup the fallback transfer syntaxes
+    std::set<std::string> fallbackSyntaxes;
+    fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax);
+    fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax);
+    fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax);
+    fallbackSyntaxes.erase(preferredTransferSyntax);
+
+    // Flatten an array with the fallback transfer syntaxes
+    std::vector<const char*> asFallback;
+    asFallback.reserve(fallbackSyntaxes.size());
+    for (std::set<std::string>::const_iterator 
+           it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it)
+    {
+      asFallback.push_back(it->c_str());
+    }
+
+    CheckStorageSOPClassesInvariant();
+    unsigned int presentationContextId = 1;
+
+    for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin();
+         it != reservedStorageSOPClasses_.end(); ++it)
+    {
+      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                              *it, asPreferred, asFallback);
+    }
+
+    for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin();
+         it != storageSOPClasses_.end(); ++it)
+    {
+      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                              *it, asPreferred, asFallback);
+    }
+
+    for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin();
+         it != defaultStorageSOPClasses_.end(); ++it)
+    {
+      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                              *it, asPreferred, asFallback);
+    }
+  }
+
+
+  static bool IsGenericTransferSyntax(const std::string& syntax)
+  {
+    return (syntax == UID_LittleEndianExplicitTransferSyntax ||
+            syntax == UID_BigEndianExplicitTransferSyntax ||
+            syntax == UID_LittleEndianImplicitTransferSyntax);
+  }
+
+
+  void DicomUserConnection::PImpl::Store(DcmInputStream& is, 
+                                         DicomUserConnection& connection,
+                                         const std::string& moveOriginatorAET,
+                                         uint16_t moveOriginatorID)
+  {
+    CheckIsOpen();
+
+    DcmFileFormat dcmff;
+    Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength));
+
+    // Determine the storage SOP class UID for this instance
+    static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016);
+    OFString sopClassUid;
+    if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good())
+    {
+      connection.AddStorageSOPClass(sopClassUid.c_str());
+    }
+
+    // Determine whether a new presentation context must be
+    // negotiated, depending on the transfer syntax of this instance
+    DcmXfer xfer(dcmff.getDataset()->getOriginalXfer());
+    const std::string syntax(xfer.getXferID());
+    bool isGeneric = IsGenericTransferSyntax(syntax);
+
+    bool renegotiate;
+    if (isGeneric)
+    {
+      // Are we making a generic-to-specific or specific-to-generic change of
+      // the transfer syntax? If this is the case, renegotiate the connection.
+      renegotiate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax());
+    }
+    else
+    {
+      // We are using a specific transfer syntax. Renegotiate if the
+      // current connection does not match this transfer syntax.
+      renegotiate = (syntax != connection.GetPreferredTransferSyntax());
+    }
+
+    if (renegotiate)
+    {
+      LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated";
+
+      if (isGeneric)
+      {
+        connection.ResetPreferredTransferSyntax();
+      }
+      else
+      {
+        connection.SetPreferredTransferSyntax(syntax);
+      }
+    }
+
+    if (!connection.IsOpen())
+    {
+      LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters";
+      connection.Open();
+    }
+
+    // Figure out which SOP class and SOP instance is encapsulated in the file
+    DIC_UI sopClass;
+    DIC_UI sopInstance;
+    if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance))
+    {
+      throw OrthancException(ErrorCode_NoSopClassOrInstance);
+    }
+
+    // Figure out which of the accepted presentation contexts should be used
+    int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass);
+    if (presID == 0)
+    {
+      const char *modalityName = dcmSOPClassUIDToModality(sopClass);
+      if (!modalityName) modalityName = dcmFindNameOfUID(sopClass);
+      if (!modalityName) modalityName = "unknown SOP class";
+      throw OrthancException(ErrorCode_NoPresentationContext);
+    }
+
+    // Prepare the transmission of data
+    T_DIMSE_C_StoreRQ request;
+    memset(&request, 0, sizeof(request));
+    request.MessageID = assoc_->nextMsgID++;
+    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
+    request.Priority = DIMSE_PRIORITY_MEDIUM;
+    request.DataSetType = DIMSE_DATASET_PRESENT;
+    strncpy(request.AffectedSOPInstanceUID, sopInstance, DIC_UI_LEN);
+
+    if (!moveOriginatorAET.empty())
+    {
+      strncpy(request.MoveOriginatorApplicationEntityTitle, 
+              moveOriginatorAET.c_str(), DIC_AE_LEN);
+      request.opts = O_STORE_MOVEORIGINATORAETITLE;
+
+      request.MoveOriginatorID = moveOriginatorID;  // The type DIC_US is an alias for uint16_t
+      request.opts |= O_STORE_MOVEORIGINATORID;
+    }
+
+    // Finally conduct transmission of data
+    T_DIMSE_C_StoreRSP rsp;
+    DcmDataset* statusDetail = NULL;
+    Check(DIMSE_storeUser(assoc_, presID, &request,
+                          NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL,
+                          /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ dimseTimeout_,
+                          &rsp, &statusDetail, NULL));
+
+    if (statusDetail != NULL) 
+    {
+      delete statusDetail;
+    }
+  }
+
+
+  namespace
+  {
+    struct FindPayload
+    {
+      DicomFindAnswers* answers;
+      const char*       level;
+      bool              isWorklist;
+    };
+  }
+
+
+  static void FindCallback(
+    /* in */
+    void *callbackData,
+    T_DIMSE_C_FindRQ *request,      /* original find request */
+    int responseCount,
+    T_DIMSE_C_FindRSP *response,    /* pending response received */
+    DcmDataset *responseIdentifiers /* pending response identifiers */
+    )
+  {
+    FindPayload& payload = *reinterpret_cast<FindPayload*>(callbackData);
+
+    if (responseIdentifiers != NULL)
+    {
+      if (payload.isWorklist)
+      {
+        ParsedDicomFile answer(*responseIdentifiers);
+        payload.answers->Add(answer);
+      }
+      else
+      {
+        DicomMap m;
+        FromDcmtkBridge::ExtractDicomSummary(m, *responseIdentifiers);
+        
+        if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL))
+        {
+          m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level, false);
+        }
+
+        payload.answers->Add(m);
+      }
+    }
+  }
+
+
+  static void FixFindQuery(DicomMap& fixedQuery,
+                           ResourceType level,
+                           const DicomMap& fields)
+  {
+    std::set<DicomTag> allowedTags;
+
+    // WARNING: Do not add "break" or reorder items in this switch-case!
+    switch (level)
+    {
+      case ResourceType_Instance:
+        DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance);
+
+      case ResourceType_Series:
+        DicomTag::AddTagsForModule(allowedTags, DicomModule_Series);
+
+      case ResourceType_Study:
+        DicomTag::AddTagsForModule(allowedTags, DicomModule_Study);
+
+      case ResourceType_Patient:
+        DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES);
+        allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES);
+        allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES);
+        break;
+
+      case ResourceType_Study:
+        allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY);
+        allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES);
+        allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES);
+        allowedTags.insert(DICOM_TAG_SOP_CLASSES_IN_STUDY);
+        break;
+
+      case ResourceType_Series:
+        allowedTags.insert(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES);
+        break;
+
+      default:
+        break;
+    }
+
+    allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET);
+
+    DicomArray query(fields);
+    for (size_t i = 0; i < query.GetSize(); i++)
+    {
+      const DicomTag& tag = query.GetElement(i).GetTag();
+      if (allowedTags.find(tag) == allowedTags.end())
+      {
+        LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag;
+      }
+      else
+      {
+        fixedQuery.SetValue(tag, query.GetElement(i).GetValue());
+      }
+    }
+  }
+
+
+  static ParsedDicomFile* ConvertQueryFields(const DicomMap& fields,
+                                             ModalityManufacturer manufacturer)
+  {
+    // Fix outgoing C-Find requests issue for Syngo.Via and its
+    // solution was reported by Emsy Chan by private mail on
+    // 2015-06-17. According to Robert van Ommen (2015-11-30), the
+    // same fix is required for Agfa Impax. This was generalized for
+    // generic manufacturer since it seems to affect PhilipsADW,
+    // GEWAServer as well:
+    // https://bitbucket.org/sjodogne/orthanc/issues/31/
+
+    switch (manufacturer)
+    {
+      case ModalityManufacturer_GenericNoWildcardInDates:
+      case ModalityManufacturer_GenericNoUniversalWildcard:
+      {
+        std::auto_ptr<DicomMap> fix(fields.Clone());
+
+        std::set<DicomTag> tags;
+        fix->GetTags(tags);
+
+        for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it)
+        {
+          // Replace a "*" wildcard query by an empty query ("") for
+          // "date" or "all" value representations depending on the
+          // type of manufacturer.
+          if (manufacturer == ModalityManufacturer_GenericNoUniversalWildcard ||
+              (manufacturer == ModalityManufacturer_GenericNoWildcardInDates &&
+               FromDcmtkBridge::LookupValueRepresentation(*it) == ValueRepresentation_Date))
+          {
+            const DicomValue* value = fix->TestAndGetValue(*it);
+
+            if (value != NULL && 
+                !value->IsNull() &&
+                value->GetContent() == "*")
+            {
+              fix->SetValue(*it, "", false);
+            }
+          }
+        }
+
+        return new ParsedDicomFile(*fix);
+      }
+
+      default:
+        return new ParsedDicomFile(fields);
+    }
+  }
+
+
+  static void ExecuteFind(DicomFindAnswers& answers,
+                          T_ASC_Association* association,
+                          DcmDataset* dataset,
+                          const char* sopClass,
+                          bool isWorklist,
+                          const char* level,
+                          uint32_t dimseTimeout)
+  {
+    assert(isWorklist ^ (level != NULL));
+
+    FindPayload payload;
+    payload.answers = &answers;
+    payload.level = level;
+    payload.isWorklist = isWorklist;
+
+    // Figure out which of the accepted presentation contexts should be used
+    int presID = ASC_findAcceptedPresentationContextID(association, sopClass);
+    if (presID == 0)
+    {
+      throw OrthancException(ErrorCode_DicomFindUnavailable);
+    }
+
+    T_DIMSE_C_FindRQ request;
+    memset(&request, 0, sizeof(request));
+    request.MessageID = association->nextMsgID++;
+    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
+    request.Priority = DIMSE_PRIORITY_MEDIUM;
+    request.DataSetType = DIMSE_DATASET_PRESENT;
+
+    T_DIMSE_C_FindRSP response;
+    DcmDataset* statusDetail = NULL;
+    OFCondition cond = DIMSE_findUser(association, presID, &request, dataset,
+                                      FindCallback, &payload,
+                                      /*opt_blockMode*/ DIMSE_BLOCKING, 
+                                      /*opt_dimse_timeout*/ dimseTimeout,
+                                      &response, &statusDetail);
+
+    if (statusDetail)
+    {
+      delete statusDetail;
+    }
+
+    Check(cond);
+  }
+
+
+  void DicomUserConnection::Find(DicomFindAnswers& result,
+                                 ResourceType level,
+                                 const DicomMap& originalFields)
+  {
+    DicomMap fields;
+    FixFindQuery(fields, level, originalFields);
+
+    CheckIsOpen();
+
+    std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_));
+    DcmDataset* dataset = query->GetDcmtkObject().getDataset();
+
+    const char* clevel = NULL;
+    const char* sopClass = NULL;
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        clevel = "PATIENT";
+        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT");
+        sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
+        break;
+
+      case ResourceType_Study:
+        clevel = "STUDY";
+        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY");
+        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
+        break;
+
+      case ResourceType_Series:
+        clevel = "SERIES";
+        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES");
+        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
+        break;
+
+      case ResourceType_Instance:
+        clevel = "INSTANCE";
+        if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
+            manufacturer_ == ModalityManufacturer_Dcm4Chee)
+        {
+          // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
+          // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
+          // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
+          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE");
+        }
+        else
+        {
+          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE");
+        }
+
+        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    // Add the expected tags for this query level.
+    // WARNING: Do not reorder or add "break" in this switch-case!
+    switch (level)
+    {
+      case ResourceType_Instance:
+        // SOP Instance UID
+        if (!fields.HasTag(0x0008, 0x0018))
+          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0018), "");
+
+      case ResourceType_Series:
+        // Series instance UID
+        if (!fields.HasTag(0x0020, 0x000e))
+          DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000e), "");
+
+      case ResourceType_Study:
+        // Accession number
+        if (!fields.HasTag(0x0008, 0x0050))
+          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0050), "");
+
+        // Study instance UID
+        if (!fields.HasTag(0x0020, 0x000d))
+          DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000d), "");
+
+      case ResourceType_Patient:
+        // Patient ID
+        if (!fields.HasTag(0x0010, 0x0020))
+          DU_putStringDOElement(dataset, DcmTagKey(0x0010, 0x0020), "");
+
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(clevel != NULL && sopClass != NULL);
+    ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, false, clevel, pimpl_->dimseTimeout_);
+  }
+
+
+  void DicomUserConnection::MoveInternal(const std::string& targetAet,
+                                         ResourceType level,
+                                         const DicomMap& fields)
+  {
+    CheckIsOpen();
+
+    std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_));
+    DcmDataset* dataset = query->GetDcmtkObject().getDataset();
+
+    const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel;
+    switch (level)
+    {
+      case ResourceType_Patient:
+        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT");
+        break;
+
+      case ResourceType_Study:
+        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY");
+        break;
+
+      case ResourceType_Series:
+        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES");
+        break;
+
+      case ResourceType_Instance:
+        if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
+            manufacturer_ == ModalityManufacturer_Dcm4Chee)
+        {
+          // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
+          // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
+          // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
+          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE");
+        }
+        else
+        {
+          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE");
+        }
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    // Figure out which of the accepted presentation contexts should be used
+    int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass);
+    if (presID == 0)
+    {
+      throw OrthancException(ErrorCode_DicomMoveUnavailable);
+    }
+
+    T_DIMSE_C_MoveRQ request;
+    memset(&request, 0, sizeof(request));
+    request.MessageID = pimpl_->assoc_->nextMsgID++;
+    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
+    request.Priority = DIMSE_PRIORITY_MEDIUM;
+    request.DataSetType = DIMSE_DATASET_PRESENT;
+    strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN);
+
+    T_DIMSE_C_MoveRSP response;
+    DcmDataset* statusDetail = NULL;
+    DcmDataset* responseIdentifiers = NULL;
+    OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset,
+                                      NULL, NULL,
+                                      /*opt_blockMode*/ DIMSE_BLOCKING, 
+                                      /*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
+                                      pimpl_->net_, NULL, NULL,
+                                      &response, &statusDetail, &responseIdentifiers);
+
+    if (statusDetail)
+    {
+      delete statusDetail;
+    }
+
+    if (responseIdentifiers)
+    {
+      delete responseIdentifiers;
+    }
+
+    Check(cond);
+  }
+
+
+  void DicomUserConnection::ResetStorageSOPClasses()
+  {
+    CheckStorageSOPClassesInvariant();
+
+    storageSOPClasses_.clear();
+    defaultStorageSOPClasses_.clear();
+
+    // Copy the short list of storage SOP classes from DCMTK, making
+    // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**).
+
+    std::set<std::string> uncommon;
+    uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_ColorSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage);
+
+    // Add the storage syntaxes for C-STORE
+    for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++)
+    {
+      if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end())
+      {
+        defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]);
+      }
+    }
+
+    CheckStorageSOPClassesInvariant();
+  }
+
+
+  DicomUserConnection::DicomUserConnection() : 
+    pimpl_(new PImpl),
+    preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX),
+    localAet_("STORESCU"),
+    remoteAet_("ANY-SCP"),
+    remoteHost_("127.0.0.1")
+  {
+    remotePort_ = 104;
+    manufacturer_ = ModalityManufacturer_Generic;
+
+    SetTimeout(defaultTimeout_);
+    pimpl_->net_ = NULL;
+    pimpl_->params_ = NULL;
+    pimpl_->assoc_ = NULL;
+
+    // SOP classes for C-ECHO, C-FIND and C-MOVE (**)
+    reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass);
+    reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
+    reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
+    reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
+    reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel);
+
+    ResetStorageSOPClasses();
+  }
+
+  DicomUserConnection::~DicomUserConnection()
+  {
+    Close();
+  }
+
+
+  void DicomUserConnection::SetRemoteModality(const RemoteModalityParameters& parameters)
+  {
+    SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle());
+    SetRemoteHost(parameters.GetHost());
+    SetRemotePort(parameters.GetPort());
+    SetRemoteManufacturer(parameters.GetManufacturer());
+  }
+
+
+  void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet)
+  {
+    if (localAet_ != aet)
+    {
+      Close();
+      localAet_ = aet;
+    }
+  }
+
+  void DicomUserConnection::SetRemoteApplicationEntityTitle(const std::string& aet)
+  {
+    if (remoteAet_ != aet)
+    {
+      Close();
+      remoteAet_ = aet;
+    }
+  }
+
+  void DicomUserConnection::SetRemoteManufacturer(ModalityManufacturer manufacturer)
+  {
+    if (manufacturer_ != manufacturer)
+    {
+      Close();
+      manufacturer_ = manufacturer;
+    }
+  }
+
+  void DicomUserConnection::ResetPreferredTransferSyntax()
+  {
+    SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX);
+  }
+
+  void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax)
+  {
+    if (preferredTransferSyntax_ != preferredTransferSyntax)
+    {
+      Close();
+      preferredTransferSyntax_ = preferredTransferSyntax;
+    }
+  }
+
+
+  void DicomUserConnection::SetRemoteHost(const std::string& host)
+  {
+    if (remoteHost_ != host)
+    {
+      if (host.size() > HOST_NAME_MAX - 10)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      Close();
+      remoteHost_ = host;
+    }
+  }
+
+  void DicomUserConnection::SetRemotePort(uint16_t port)
+  {
+    if (remotePort_ != port)
+    {
+      Close();
+      remotePort_ = port;
+    }
+  }
+
+  void DicomUserConnection::Open()
+  {
+    if (IsOpen())
+    {
+      // Don't reopen the connection
+      return;
+    }
+
+    LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() 
+              << "\" to AET \"" << GetRemoteApplicationEntityTitle() << "\" on host "
+              << GetRemoteHost() << ":" << GetRemotePort() 
+              << " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")";
+
+    Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_));
+    Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU));
+
+    // Set this application's title and the called application's title in the params
+    Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL));
+
+    // Set the network addresses of the local and remote entities
+    char localHost[HOST_NAME_MAX];
+    gethostname(localHost, HOST_NAME_MAX - 1);
+
+    char remoteHostAndPort[HOST_NAME_MAX];
+
+#ifdef _MSC_VER
+    _snprintf
+#else
+      snprintf
+#endif
+      (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_);
+
+    Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort));
+
+    // Set various options
+    Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false));
+
+    SetupPresentationContexts(preferredTransferSyntax_);
+
+    // Do the association
+    Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_));
+
+    if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0)
+    {
+      throw OrthancException(ErrorCode_NoPresentationContext);
+    }
+  }
+
+  void DicomUserConnection::Close()
+  {
+    if (pimpl_->assoc_ != NULL)
+    {
+      ASC_releaseAssociation(pimpl_->assoc_);
+      ASC_destroyAssociation(&pimpl_->assoc_);
+      pimpl_->assoc_ = NULL;
+      pimpl_->params_ = NULL;
+    }
+    else
+    {
+      if (pimpl_->params_ != NULL)
+      {
+        ASC_destroyAssociationParameters(&pimpl_->params_);
+        pimpl_->params_ = NULL;
+      }
+    }
+
+    if (pimpl_->net_ != NULL)
+    {
+      ASC_dropNetwork(&pimpl_->net_);
+      pimpl_->net_ = NULL;
+    }
+  }
+
+  bool DicomUserConnection::IsOpen() const
+  {
+    return pimpl_->IsOpen();
+  }
+
+  void DicomUserConnection::Store(const char* buffer, 
+                                  size_t size,
+                                  const std::string& moveOriginatorAET,
+                                  uint16_t moveOriginatorID)
+  {
+    // Prepare an input stream for the memory buffer
+    DcmInputBufferStream is;
+    if (size > 0)
+      is.setBuffer(buffer, size);
+    is.setEos();
+      
+    pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID);
+  }
+
+  void DicomUserConnection::Store(const std::string& buffer,
+                                  const std::string& moveOriginatorAET,
+                                  uint16_t moveOriginatorID)
+  {
+    if (buffer.size() > 0)
+      Store(reinterpret_cast<const char*>(&buffer[0]), buffer.size(), moveOriginatorAET, moveOriginatorID);
+    else
+      Store(NULL, 0, moveOriginatorAET, moveOriginatorID);
+  }
+
+  void DicomUserConnection::StoreFile(const std::string& path,
+                                      const std::string& moveOriginatorAET,
+                                      uint16_t moveOriginatorID)
+  {
+    // Prepare an input stream for the file
+    DcmInputFileStream is(path.c_str());
+    pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID);
+  }
+
+  bool DicomUserConnection::Echo()
+  {
+    CheckIsOpen();
+    DIC_US status;
+    Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, 
+                         /*opt_blockMode*/ DIMSE_BLOCKING, 
+                         /*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
+                         &status, NULL));
+    return status == STATUS_Success;
+  }
+
+
+  static void TestAndCopyTag(DicomMap& result,
+                             const DicomMap& source,
+                             const DicomTag& tag)
+  {
+    if (!source.HasTag(tag))
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+    else
+    {
+      result.SetValue(tag, source.GetValue(tag));
+    }
+  }
+
+
+  void DicomUserConnection::Move(const std::string& targetAet,
+                                 ResourceType level,
+                                 const DicomMap& findResult)
+  {
+    DicomMap move;
+    switch (level)
+    {
+      case ResourceType_Patient:
+        TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID);
+        break;
+
+      case ResourceType_Study:
+        TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID);
+        break;
+
+      case ResourceType_Series:
+        TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID);
+        TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID);
+        break;
+
+      case ResourceType_Instance:
+        TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID);
+        TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID);
+        TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    MoveInternal(targetAet, level, move);
+  }
+
+
+  void DicomUserConnection::Move(const std::string& targetAet,
+                                 const DicomMap& findResult)
+  {
+    if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent();
+    ResourceType level = StringToResourceType(tmp.c_str());
+
+    Move(targetAet, level, findResult);
+  }
+
+
+  void DicomUserConnection::MovePatient(const std::string& targetAet,
+                                        const std::string& patientId)
+  {
+    DicomMap query;
+    query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false);
+    MoveInternal(targetAet, ResourceType_Patient, query);
+  }
+
+  void DicomUserConnection::MoveStudy(const std::string& targetAet,
+                                      const std::string& studyUid)
+  {
+    DicomMap query;
+    query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false);
+    MoveInternal(targetAet, ResourceType_Study, query);
+  }
+
+  void DicomUserConnection::MoveSeries(const std::string& targetAet,
+                                       const std::string& studyUid,
+                                       const std::string& seriesUid)
+  {
+    DicomMap query;
+    query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false);
+    query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false);
+    MoveInternal(targetAet, ResourceType_Series, query);
+  }
+
+  void DicomUserConnection::MoveInstance(const std::string& targetAet,
+                                         const std::string& studyUid,
+                                         const std::string& seriesUid,
+                                         const std::string& instanceUid)
+  {
+    DicomMap query;
+    query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false);
+    query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false);
+    query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false);
+    MoveInternal(targetAet, ResourceType_Instance, query);
+  }
+
+
+  void DicomUserConnection::SetTimeout(uint32_t seconds)
+  {
+    if (seconds == 0)
+    {
+      DisableTimeout();
+    }
+    else
+    {
+      dcmConnectionTimeout.set(seconds);
+      pimpl_->dimseTimeout_ = seconds;
+      pimpl_->acseTimeout_ = 10;  // Timeout used during association negociation
+    }
+  }
+
+
+  void DicomUserConnection::DisableTimeout()
+  {
+    /**
+     * Global timeout (seconds) for connecting to remote hosts.
+     * Default value is -1 which selects infinite timeout, i.e. blocking connect().
+     */
+    dcmConnectionTimeout.set(-1);
+    pimpl_->dimseTimeout_ = 0;
+    pimpl_->acseTimeout_ = 10;  // Timeout used during association negociation
+  }
+
+
+  void DicomUserConnection::CheckStorageSOPClassesInvariant() const
+  {
+    assert(storageSOPClasses_.size() + 
+           defaultStorageSOPClasses_.size() + 
+           reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES);
+  }
+
+  void DicomUserConnection::AddStorageSOPClass(const char* sop)
+  {
+    CheckStorageSOPClassesInvariant();
+
+    if (storageSOPClasses_.find(sop) != storageSOPClasses_.end())
+    {
+      // This storage SOP class is already explicitly registered. Do
+      // nothing.
+      return;
+    }
+
+    if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end())
+    {
+      // This storage SOP class is not explicitly registered, but is
+      // used by default. Just register it explicitly.
+      defaultStorageSOPClasses_.erase(sop);
+      storageSOPClasses_.insert(sop);
+
+      CheckStorageSOPClassesInvariant();
+      return;
+    }
+
+    // This storage SOP class is neither explicitly, nor implicitly
+    // registered. Close the connection and register it explicitly.
+
+    Close();
+
+    if (reservedStorageSOPClasses_.size() + 
+        storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)  // (*)
+    {
+      // The maximum number of SOP classes is reached
+      ResetStorageSOPClasses();
+      defaultStorageSOPClasses_.erase(sop);
+    }
+    else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + 
+             defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)
+    {
+      // Make room in the default storage syntaxes
+      assert(!defaultStorageSOPClasses_.empty());  // Necessarily true because condition (*) is false
+      defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin());
+    }
+
+    // Explicitly register the new storage syntax
+    storageSOPClasses_.insert(sop);
+
+    CheckStorageSOPClassesInvariant();
+  }
+
+
+  void DicomUserConnection::FindWorklist(DicomFindAnswers& result,
+                                         ParsedDicomFile& query)
+  {
+    CheckIsOpen();
+
+    DcmDataset* dataset = query.GetDcmtkObject().getDataset();
+    const char* sopClass = UID_FINDModalityWorklistInformationModel;
+
+    ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true, NULL, pimpl_->dimseTimeout_);
+  }
+
+  
+  void DicomUserConnection::SetDefaultTimeout(uint32_t seconds)
+  {
+    LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " 
+              << seconds << " seconds (0 = no timeout)";
+    defaultTimeout_ = seconds;
+  }  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/DicomUserConnection.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,205 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1
+#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1
+#endif
+
+#include "DicomFindAnswers.h"
+#include "../Enumerations.h"
+#include "RemoteModalityParameters.h"
+
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+#include <list>
+
+namespace Orthanc
+{
+  class DicomUserConnection : public boost::noncopyable
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    // Connection parameters
+    std::string preferredTransferSyntax_;
+    std::string localAet_;
+    std::string remoteAet_;
+    std::string remoteHost_;
+    uint16_t remotePort_;
+    ModalityManufacturer manufacturer_;
+    std::set<std::string> storageSOPClasses_;
+    std::list<std::string> reservedStorageSOPClasses_;
+    std::set<std::string> defaultStorageSOPClasses_;
+
+    void CheckIsOpen() const;
+
+    void SetupPresentationContexts(const std::string& preferredTransferSyntax);
+
+    void MoveInternal(const std::string& targetAet,
+                      ResourceType level,
+                      const DicomMap& fields);
+
+    void ResetStorageSOPClasses();
+
+    void CheckStorageSOPClassesInvariant() const;
+
+  public:
+    DicomUserConnection();
+
+    ~DicomUserConnection();
+
+    void SetRemoteModality(const RemoteModalityParameters& parameters);
+
+    void SetLocalApplicationEntityTitle(const std::string& aet);
+
+    const std::string& GetLocalApplicationEntityTitle() const
+    {
+      return localAet_;
+    }
+
+    void SetRemoteApplicationEntityTitle(const std::string& aet);
+
+    const std::string& GetRemoteApplicationEntityTitle() const
+    {
+      return remoteAet_;
+    }
+
+    void SetRemoteHost(const std::string& host);
+
+    const std::string& GetRemoteHost() const
+    {
+      return remoteHost_;
+    }
+
+    void SetRemotePort(uint16_t port);
+
+    uint16_t GetRemotePort() const
+    {
+      return remotePort_;
+    }
+
+    void SetRemoteManufacturer(ModalityManufacturer manufacturer);
+
+    ModalityManufacturer GetRemoteManufacturer() const
+    {
+      return manufacturer_;
+    }
+
+    void ResetPreferredTransferSyntax();
+
+    void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax);
+
+    const std::string& GetPreferredTransferSyntax() const
+    {
+      return preferredTransferSyntax_;
+    }
+
+    void AddStorageSOPClass(const char* sop);
+
+    void Open();
+
+    void Close();
+
+    bool IsOpen() const;
+
+    bool Echo();
+
+    void Store(const char* buffer, 
+               size_t size,
+               const std::string& moveOriginatorAET,
+               uint16_t moveOriginatorID);
+
+    void Store(const char* buffer, 
+               size_t size)
+    {
+      Store(buffer, size, "", 0);  // Not a C-Move
+    }
+
+    void Store(const std::string& buffer,
+               const std::string& moveOriginatorAET,
+               uint16_t moveOriginatorID);
+
+    void Store(const std::string& buffer)
+    {
+      Store(buffer, "", 0);  // Not a C-Move
+    }
+
+    void StoreFile(const std::string& path,
+                   const std::string& moveOriginatorAET,
+                   uint16_t moveOriginatorID);
+
+    void StoreFile(const std::string& path)
+    {
+      StoreFile(path, "", 0);  // Not a C-Move
+    }
+
+    void Find(DicomFindAnswers& result,
+              ResourceType level,
+              const DicomMap& fields);
+
+    void Move(const std::string& targetAet,
+              ResourceType level,
+              const DicomMap& findResult);
+
+    void Move(const std::string& targetAet,
+              const DicomMap& findResult);
+
+    void MovePatient(const std::string& targetAet,
+                     const std::string& patientId);
+
+    void MoveStudy(const std::string& targetAet,
+                   const std::string& studyUid);
+
+    void MoveSeries(const std::string& targetAet,
+                    const std::string& studyUid,
+                    const std::string& seriesUid);
+
+    void MoveInstance(const std::string& targetAet,
+                      const std::string& studyUid,
+                      const std::string& seriesUid,
+                      const std::string& instanceUid);
+
+    void SetTimeout(uint32_t seconds);
+
+    void DisableTimeout();
+
+    void FindWorklist(DicomFindAnswers& result,
+                      ParsedDicomFile& query);
+
+    static void SetDefaultTimeout(uint32_t seconds);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IApplicationEntityFilter.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,67 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+
+#include <string>
+
+namespace Orthanc
+{
+  class IApplicationEntityFilter : public boost::noncopyable
+  {
+  public:
+    virtual ~IApplicationEntityFilter()
+    {
+    }
+
+    virtual bool IsAllowedConnection(const std::string& remoteIp,
+                                     const std::string& remoteAet,
+                                     const std::string& calledAet) = 0;
+
+    virtual bool IsAllowedRequest(const std::string& remoteIp,
+                                  const std::string& remoteAet,
+                                  const std::string& calledAet,
+                                  DicomRequestType type) = 0;
+
+    virtual bool IsAllowedTransferSyntax(const std::string& remoteIp,
+                                         const std::string& remoteAet,
+                                         const std::string& calledAet,
+                                         TransferSyntax syntax) = 0;
+
+    virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
+                                           const std::string& remoteAet,
+                                           const std::string& calledAet) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IFindRequestHandler.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,55 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomFindAnswers.h"
+
+namespace Orthanc
+{
+  class IFindRequestHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IFindRequestHandler()
+    {
+    }
+
+    virtual void Handle(DicomFindAnswers& answers,
+                        const DicomMap& input,
+                        const std::list<DicomTag>& sequencesToReturn,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet,
+                        ModalityManufacturer manufacturer) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IFindRequestHandlerFactory.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IFindRequestHandler.h"
+
+namespace Orthanc
+{
+  class IFindRequestHandlerFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~IFindRequestHandlerFactory()
+    {
+    }
+
+    virtual IFindRequestHandler* ConstructFindRequestHandler() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IMoveRequestHandler.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,79 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Core/DicomFormat/DicomMap.h"
+
+#include <vector>
+#include <string>
+
+
+namespace Orthanc
+{
+  class IMoveRequestIterator : public boost::noncopyable
+  {
+  public:
+    enum Status
+    {
+      Status_Success,
+      Status_Failure,
+      Status_Warning
+    };
+
+    virtual ~IMoveRequestIterator()
+    {
+    }
+
+    virtual unsigned int GetSubOperationCount() const = 0;
+
+    virtual Status DoNext() = 0;
+  };
+
+
+  class IMoveRequestHandler
+  {
+  public:
+    virtual ~IMoveRequestHandler()
+    {
+    }
+
+    virtual IMoveRequestIterator* Handle(const std::string& targetAet,
+                                         const DicomMap& input,
+                                         const std::string& originatorIp,
+                                         const std::string& originatorAet,
+                                         const std::string& calledAet,
+                                         uint16_t originatorId) = 0;
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IMoveRequestHandlerFactory.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IMoveRequestHandler.h"
+
+namespace Orthanc
+{
+  class IMoveRequestHandlerFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~IMoveRequestHandlerFactory()
+    {
+    }
+
+    virtual IMoveRequestHandler* ConstructMoveRequestHandler() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IStoreRequestHandler.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,58 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Core/DicomFormat/DicomMap.h"
+
+#include <vector>
+#include <string>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class IStoreRequestHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IStoreRequestHandler()
+    {
+    }
+
+    virtual void Handle(const std::string& dicomFile,
+                        const DicomMap& dicomSummary,
+                        const Json::Value& dicomJson,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IStoreRequestHandlerFactory.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IStoreRequestHandler.h"
+
+namespace Orthanc
+{
+  class IStoreRequestHandlerFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~IStoreRequestHandlerFactory()
+    {
+    }
+
+    virtual IStoreRequestHandler* ConstructStoreRequestHandler() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IWorklistRequestHandler.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomFindAnswers.h"
+
+namespace Orthanc
+{
+  class IWorklistRequestHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IWorklistRequestHandler()
+    {
+    }
+
+    virtual void Handle(DicomFindAnswers& answers,
+                        ParsedDicomFile& query,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet,
+                        ModalityManufacturer manufacturer) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IWorklistRequestHandlerFactory.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IWorklistRequestHandler.h"
+
+namespace Orthanc
+{
+  class IWorklistRequestHandlerFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~IWorklistRequestHandlerFactory()
+    {
+    }
+
+    virtual IWorklistRequestHandler* ConstructWorklistRequestHandler() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/Internals/CommandDispatcher.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,934 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
+#include "../../PrecompiledHeaders.h"
+#include "CommandDispatcher.h"
+
+#include "FindScp.h"
+#include "StoreScp.h"
+#include "MoveScp.h"
+#include "../../Toolbox.h"
+#include "../../Logging.h"
+
+#include <dcmtk/dcmnet/dcasccfg.h>      /* for class DcmAssociationConfiguration */
+#include <boost/lexical_cast.hpp>
+
+static OFBool    opt_rejectWithoutImplementationUID = OFFalse;
+
+
+
+static DUL_PRESENTATIONCONTEXT *
+findPresentationContextID(LST_HEAD * head,
+                          T_ASC_PresentationContextID presentationContextID)
+{
+  DUL_PRESENTATIONCONTEXT *pc;
+  LST_HEAD **l;
+  OFBool found = OFFalse;
+
+  if (head == NULL)
+    return NULL;
+
+  l = &head;
+  if (*l == NULL)
+    return NULL;
+
+  pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l));
+  (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc));
+
+  while (pc && !found) {
+    if (pc->presentationContextID == presentationContextID) {
+      found = OFTrue;
+    } else {
+      pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l));
+    }
+  }
+  return pc;
+}
+
+
+/** accept all presenstation contexts for unknown SOP classes,
+ *  i.e. UIDs appearing in the list of abstract syntaxes
+ *  where no corresponding name is defined in the UID dictionary.
+ *  @param params pointer to association parameters structure
+ *  @param transferSyntax transfer syntax to accept
+ *  @param acceptedRole SCU/SCP role to accept
+ */
+static OFCondition acceptUnknownContextsWithTransferSyntax(
+  T_ASC_Parameters * params,
+  const char* transferSyntax,
+  T_ASC_SC_ROLE acceptedRole)
+{
+  OFCondition cond = EC_Normal;
+  int n, i, k;
+  DUL_PRESENTATIONCONTEXT *dpc;
+  T_ASC_PresentationContext pc;
+  OFBool accepted = OFFalse;
+  OFBool abstractOK = OFFalse;
+
+  n = ASC_countPresentationContexts(params);
+  for (i = 0; i < n; i++)
+  {
+    cond = ASC_getPresentationContext(params, i, &pc);
+    if (cond.bad()) return cond;
+    abstractOK = OFFalse;
+    accepted = OFFalse;
+
+    if (dcmFindNameOfUID(pc.abstractSyntax) == NULL)
+    {
+      abstractOK = OFTrue;
+
+      /* check the transfer syntax */
+      for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++)
+      {
+        if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0)
+        {
+          accepted = OFTrue;
+        }
+      }
+    }
+
+    if (accepted)
+    {
+      cond = ASC_acceptPresentationContext(
+        params, pc.presentationContextID,
+        transferSyntax, acceptedRole);
+      if (cond.bad()) return cond;
+    } else {
+      T_ASC_P_ResultReason reason;
+
+      /* do not refuse if already accepted */
+      dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext,
+                                      pc.presentationContextID);
+      if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE)))
+      {
+
+        if (abstractOK) {
+          reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;
+        } else {
+          reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED;
+        }
+        /*
+         * If previously this presentation context was refused
+         * because of bad transfer syntax let it stay that way.
+         */
+        if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED))
+          reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;
+
+        cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason);
+        if (cond.bad()) return cond;
+      }
+    }
+  }
+  return EC_Normal;
+}
+
+
+/** accept all presenstation contexts for unknown SOP classes,
+ *  i.e. UIDs appearing in the list of abstract syntaxes
+ *  where no corresponding name is defined in the UID dictionary.
+ *  This method is passed a list of "preferred" transfer syntaxes.
+ *  @param params pointer to association parameters structure
+ *  @param transferSyntax transfer syntax to accept
+ *  @param acceptedRole SCU/SCP role to accept
+ */
+static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes(
+  T_ASC_Parameters * params,
+  const char* transferSyntaxes[], int transferSyntaxCount,
+  T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT)
+{
+  OFCondition cond = EC_Normal;
+  /*
+  ** Accept in the order "least wanted" to "most wanted" transfer
+  ** syntax.  Accepting a transfer syntax will override previously
+  ** accepted transfer syntaxes.
+  */
+  for (int i = transferSyntaxCount - 1; i >= 0; i--)
+  {
+    cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole);
+    if (cond.bad()) return cond;
+  }
+  return cond;
+}
+
+
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    /**
+     * EXTRACT OF FILE "dcmdata/libsrc/dcuid.cc" FROM DCMTK 3.6.0
+     * (dcmAllStorageSOPClassUIDs).
+     *
+     * an array of const strings containing all known Storage SOP
+     * Classes that fit into the conventional
+     * PATIENT-STUDY-SERIES-INSTANCE information model,
+     * i.e. everything a Storage SCP might want to store in a PACS.
+     * Special cases such as hanging protocol storage or the Storage
+     * SOP Class are not included in this list.
+     *
+     * THIS LIST CONTAINS ALL STORAGE SOP CLASSES INCLUDING RETIRED
+     * ONES AND IS LARGER THAN 64 ENTRIES.
+     */
+
+    const char* orthancStorageSOPClassUIDs[] =
+    {
+      UID_AmbulatoryECGWaveformStorage,
+      UID_ArterialPulseWaveformStorage,
+      UID_AutorefractionMeasurementsStorage,
+      UID_BasicStructuredDisplayStorage,
+      UID_BasicTextSRStorage,
+      UID_BasicVoiceAudioWaveformStorage,
+      UID_BlendingSoftcopyPresentationStateStorage,
+      UID_BreastTomosynthesisImageStorage,
+      UID_CardiacElectrophysiologyWaveformStorage,
+      UID_ChestCADSRStorage,
+      UID_ColonCADSRStorage,
+      UID_ColorSoftcopyPresentationStateStorage,
+      UID_ComprehensiveSRStorage,
+      UID_ComputedRadiographyImageStorage,
+      UID_CTImageStorage,
+      UID_DeformableSpatialRegistrationStorage,
+      UID_DigitalIntraOralXRayImageStorageForPresentation,
+      UID_DigitalIntraOralXRayImageStorageForProcessing,
+      UID_DigitalMammographyXRayImageStorageForPresentation,
+      UID_DigitalMammographyXRayImageStorageForProcessing,
+      UID_DigitalXRayImageStorageForPresentation,
+      UID_DigitalXRayImageStorageForProcessing,
+      UID_EncapsulatedCDAStorage,
+      UID_EncapsulatedPDFStorage,
+      UID_EnhancedCTImageStorage,
+      UID_EnhancedMRColorImageStorage,
+      UID_EnhancedMRImageStorage,
+      UID_EnhancedPETImageStorage,
+      UID_EnhancedSRStorage,
+      UID_EnhancedUSVolumeStorage,
+      UID_EnhancedXAImageStorage,
+      UID_EnhancedXRFImageStorage,
+      UID_GeneralAudioWaveformStorage,
+      UID_GeneralECGWaveformStorage,
+      UID_GenericImplantTemplateStorage,
+      UID_GrayscaleSoftcopyPresentationStateStorage,
+      UID_HemodynamicWaveformStorage,
+      UID_ImplantAssemblyTemplateStorage,
+      UID_ImplantationPlanSRDocumentStorage,
+      UID_ImplantTemplateGroupStorage,
+      UID_IntraocularLensCalculationsStorage,
+      UID_KeratometryMeasurementsStorage,
+      UID_KeyObjectSelectionDocumentStorage,
+      UID_LensometryMeasurementsStorage,
+      UID_MacularGridThicknessAndVolumeReportStorage,
+      UID_MammographyCADSRStorage,
+      UID_MRImageStorage,
+      UID_MRSpectroscopyStorage,
+      UID_MultiframeGrayscaleByteSecondaryCaptureImageStorage,
+      UID_MultiframeGrayscaleWordSecondaryCaptureImageStorage,
+      UID_MultiframeSingleBitSecondaryCaptureImageStorage,
+      UID_MultiframeTrueColorSecondaryCaptureImageStorage,
+      UID_NuclearMedicineImageStorage,
+      UID_OphthalmicAxialMeasurementsStorage,
+      UID_OphthalmicPhotography16BitImageStorage,
+      UID_OphthalmicPhotography8BitImageStorage,
+      UID_OphthalmicTomographyImageStorage,
+      UID_OphthalmicVisualFieldStaticPerimetryMeasurementsStorage,
+      UID_PositronEmissionTomographyImageStorage,
+      UID_ProcedureLogStorage,
+      UID_PseudoColorSoftcopyPresentationStateStorage,
+      UID_RawDataStorage,
+      UID_RealWorldValueMappingStorage,
+      UID_RespiratoryWaveformStorage,
+      UID_RTBeamsTreatmentRecordStorage,
+      UID_RTBrachyTreatmentRecordStorage,
+      UID_RTDoseStorage,
+      UID_RTImageStorage,
+      UID_RTIonBeamsTreatmentRecordStorage,
+      UID_RTIonPlanStorage,
+      UID_RTPlanStorage,
+      UID_RTStructureSetStorage,
+      UID_RTTreatmentSummaryRecordStorage,
+      UID_SecondaryCaptureImageStorage,
+      UID_SegmentationStorage,
+      UID_SpatialFiducialsStorage,
+      UID_SpatialRegistrationStorage,
+      UID_SpectaclePrescriptionReportStorage,
+      UID_StereometricRelationshipStorage,
+      UID_SubjectiveRefractionMeasurementsStorage,
+      UID_SurfaceSegmentationStorage,
+      UID_TwelveLeadECGWaveformStorage,
+      UID_UltrasoundImageStorage,
+      UID_UltrasoundMultiframeImageStorage,
+      UID_VideoEndoscopicImageStorage,
+      UID_VideoMicroscopicImageStorage,
+      UID_VideoPhotographicImageStorage,
+      UID_VisualAcuityMeasurementsStorage,
+      UID_VLEndoscopicImageStorage,
+      UID_VLMicroscopicImageStorage,
+      UID_VLPhotographicImageStorage,
+      UID_VLSlideCoordinatesMicroscopicImageStorage,
+      UID_VLWholeSlideMicroscopyImageStorage,
+      UID_XAXRFGrayscaleSoftcopyPresentationStateStorage,
+      UID_XRay3DAngiographicImageStorage,
+      UID_XRay3DCraniofacialImageStorage,
+      UID_XRayAngiographicImageStorage,
+      UID_XRayRadiationDoseSRStorage,
+      UID_XRayRadiofluoroscopicImageStorage,
+      // retired
+      UID_RETIRED_HardcopyColorImageStorage,
+      UID_RETIRED_HardcopyGrayscaleImageStorage,
+      UID_RETIRED_NuclearMedicineImageStorage,
+      UID_RETIRED_StandaloneCurveStorage,
+      UID_RETIRED_StandaloneModalityLUTStorage,
+      UID_RETIRED_StandaloneOverlayStorage,
+      UID_RETIRED_StandalonePETCurveStorage,
+      UID_RETIRED_StandaloneVOILUTStorage,
+      UID_RETIRED_StoredPrintStorage,
+      UID_RETIRED_UltrasoundImageStorage,
+      UID_RETIRED_UltrasoundMultiframeImageStorage,
+      UID_RETIRED_VLImageStorage,
+      UID_RETIRED_VLMultiFrameImageStorage,
+      UID_RETIRED_XRayAngiographicBiPlaneImageStorage,
+      // draft
+      UID_DRAFT_SRAudioStorage,
+      UID_DRAFT_SRComprehensiveStorage,
+      UID_DRAFT_SRDetailStorage,
+      UID_DRAFT_SRTextStorage,
+      UID_DRAFT_WaveformStorage,
+      UID_DRAFT_RTBeamsDeliveryInstructionStorage,
+      NULL
+    };
+
+    const int orthancStorageSOPClassUIDsCount = (sizeof(orthancStorageSOPClassUIDs) / sizeof(const char*)) - 1;
+
+
+
+    OFCondition AssociationCleanup(T_ASC_Association *assoc)
+    {
+      OFCondition cond = ASC_dropSCPAssociation(assoc);
+      if (cond.bad())
+      {
+        LOG(FATAL) << cond.text();
+        return cond;
+      }
+
+      cond = ASC_destroyAssociation(&assoc);
+      if (cond.bad())
+      {
+        LOG(FATAL) << cond.text();
+        return cond;
+      }
+
+      return cond;
+    }
+
+
+
+    CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net)
+    {
+      DcmAssociationConfiguration asccfg;
+      char buf[BUFSIZ];
+      T_ASC_Association *assoc;
+      OFCondition cond;
+      OFString sprofile;
+      OFString temp_str;
+
+      std::vector<const char*> knownAbstractSyntaxes;
+
+      // For C-STORE
+      if (server.HasStoreRequestHandlerFactory())
+      {
+        knownAbstractSyntaxes.push_back(UID_VerificationSOPClass);
+      }
+
+      // For C-FIND
+      if (server.HasFindRequestHandlerFactory())
+      {
+        knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
+        knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
+      }
+
+      if (server.HasWorklistRequestHandlerFactory())
+      {
+        knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel);
+      }
+
+      // For C-MOVE
+      if (server.HasMoveRequestHandlerFactory())
+      {
+        knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
+        knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel);
+      }
+
+      cond = ASC_receiveAssociation(net, &assoc, 
+                                    /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, 
+                                    NULL, NULL,
+                                    /*opt_secureConnection*/ OFFalse,
+                                    DUL_NOBLOCK, 1);
+
+      if (cond == DUL_NOASSOCIATIONREQUEST)
+      {
+        // Timeout
+        AssociationCleanup(assoc);
+        return NULL;
+      }
+
+      // if some kind of error occured, take care of it
+      if (cond.bad())
+      {
+        LOG(ERROR) << "Receiving Association failed: " << cond.text();
+        // no matter what kind of error occurred, we need to do a cleanup
+        AssociationCleanup(assoc);
+        return NULL;
+      }
+
+      // Retrieve the AET and the IP address of the remote modality
+      std::string remoteAet;
+      std::string remoteIp;
+      std::string calledAet;
+  
+      {
+        DIC_AE remoteAet_C;
+        DIC_AE calledAet_C;
+        DIC_AE remoteIp_C;
+        DIC_AE calledIP_C;
+        if (ASC_getAPTitles(assoc->params, remoteAet_C, calledAet_C, NULL).bad() ||
+            ASC_getPresentationAddresses(assoc->params, remoteIp_C, calledIP_C).bad())
+        {
+          T_ASC_RejectParameters rej =
+            {
+              ASC_RESULT_REJECTEDPERMANENT,
+              ASC_SOURCE_SERVICEUSER,
+              ASC_REASON_SU_NOREASON
+            };
+          ASC_rejectAssociation(assoc, &rej);
+          AssociationCleanup(assoc);
+          return NULL;
+        }
+
+        remoteIp = std::string(/*OFSTRING_GUARD*/(remoteIp_C));
+        remoteAet = std::string(/*OFSTRING_GUARD*/(remoteAet_C));
+        calledAet = (/*OFSTRING_GUARD*/(calledAet_C));
+      }
+
+      LOG(INFO) << "Association Received from AET " << remoteAet 
+                << " on IP " << remoteIp;
+
+
+      std::vector<const char*> transferSyntaxes;
+
+      // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1
+      transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
+      transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
+      transferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
+
+      // New transfer syntaxes supported since Orthanc 0.7.2
+      if (!server.HasApplicationEntityFilter() ||
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated))
+      {
+        transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); 
+      }
+
+      if (!server.HasApplicationEntityFilter() ||
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg))
+      {
+        transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax);
+        transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax);
+      }
+
+      if (!server.HasApplicationEntityFilter() ||
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000))
+      {
+        transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
+        transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
+      }
+
+      if (!server.HasApplicationEntityFilter() ||
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless))
+      {
+        transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
+        transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
+        transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax);
+        transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax);
+      }
+
+      if (!server.HasApplicationEntityFilter() ||
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip))
+      {
+        transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax);
+        transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax);
+      }
+
+      if (!server.HasApplicationEntityFilter() ||
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2))
+      {
+        transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax);
+        transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax);
+      }
+
+      if (!server.HasApplicationEntityFilter() ||
+          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle))
+      {
+        transferSyntaxes.push_back(UID_RLELosslessTransferSyntax);
+      }
+
+      /* accept the Verification SOP Class if presented */
+      cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), &transferSyntaxes[0], transferSyntaxes.size());
+      if (cond.bad())
+      {
+        LOG(INFO) << cond.text();
+        AssociationCleanup(assoc);
+        return NULL;
+      }
+
+      /* the array of Storage SOP Class UIDs comes from dcuid.h */
+      cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, orthancStorageSOPClassUIDs, orthancStorageSOPClassUIDsCount, &transferSyntaxes[0], transferSyntaxes.size());
+      if (cond.bad())
+      {
+        LOG(INFO) << cond.text();
+        AssociationCleanup(assoc);
+        return NULL;
+      }
+
+      if (!server.HasApplicationEntityFilter() ||
+          server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet))
+      {
+        /*
+         * Promiscous mode is enabled: Accept everything not known not
+         * to be a storage SOP class.
+         **/
+        cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
+          assoc->params, &transferSyntaxes[0], transferSyntaxes.size());
+        if (cond.bad())
+        {
+          LOG(INFO) << cond.text();
+          AssociationCleanup(assoc);
+          return NULL;
+        }
+      }
+
+      /* set our app title */
+      ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str());
+
+      /* acknowledge or reject this association */
+      cond = ASC_getApplicationContextName(assoc->params, buf);
+      if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0)
+      {
+        /* reject: the application context name is not supported */
+        T_ASC_RejectParameters rej =
+          {
+            ASC_RESULT_REJECTEDPERMANENT,
+            ASC_SOURCE_SERVICEUSER,
+            ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED
+          };
+
+        LOG(INFO) << "Association Rejected: Bad Application Context Name: " << buf;
+        cond = ASC_rejectAssociation(assoc, &rej);
+        if (cond.bad())
+        {
+          LOG(INFO) << cond.text();
+        }
+        AssociationCleanup(assoc);
+        return NULL;
+      }
+
+      /* check the AETs */
+      if (!server.IsMyAETitle(calledAet))
+      {
+        LOG(WARNING) << "Rejected association, because of a bad called AET in the request (" << calledAet << ")";
+        T_ASC_RejectParameters rej =
+          {
+            ASC_RESULT_REJECTEDPERMANENT,
+            ASC_SOURCE_SERVICEUSER,
+            ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED
+          };
+        ASC_rejectAssociation(assoc, &rej);
+        AssociationCleanup(assoc);
+        return NULL;
+      }
+
+      if (server.HasApplicationEntityFilter() &&
+          !server.GetApplicationEntityFilter().IsAllowedConnection(remoteIp, remoteAet, calledAet))
+      {
+        LOG(WARNING) << "Rejected association for remote AET " << remoteAet << " on IP " << remoteIp;
+        T_ASC_RejectParameters rej =
+          {
+            ASC_RESULT_REJECTEDPERMANENT,
+            ASC_SOURCE_SERVICEUSER,
+            ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED
+          };
+        ASC_rejectAssociation(assoc, &rej);
+        AssociationCleanup(assoc);
+        return NULL;
+      }
+
+      if (opt_rejectWithoutImplementationUID && 
+          strlen(assoc->params->theirImplementationClassUID) == 0)
+      {
+        /* reject: the no implementation Class UID provided */
+        T_ASC_RejectParameters rej =
+          {
+            ASC_RESULT_REJECTEDPERMANENT,
+            ASC_SOURCE_SERVICEUSER,
+            ASC_REASON_SU_NOREASON
+          };
+
+        LOG(INFO) << "Association Rejected: No Implementation Class UID provided";
+        cond = ASC_rejectAssociation(assoc, &rej);
+        if (cond.bad())
+        {
+          LOG(INFO) << cond.text();
+        }
+        AssociationCleanup(assoc);
+        return NULL;
+      }
+
+      {
+        cond = ASC_acknowledgeAssociation(assoc);
+        if (cond.bad())
+        {
+          LOG(ERROR) << cond.text();
+          AssociationCleanup(assoc);
+          return NULL;
+        }
+        LOG(INFO) << "Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")";
+        if (ASC_countAcceptedPresentationContexts(assoc->params) == 0)
+          LOG(INFO) << "    (but no valid presentation contexts)";
+      }
+
+      IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL;
+      return new CommandDispatcher(server, assoc, remoteIp, remoteAet, calledAet, filter);
+    }
+
+
+    CommandDispatcher::CommandDispatcher(const DicomServer& server,
+                                         T_ASC_Association* assoc,
+                                         const std::string& remoteIp,
+                                         const std::string& remoteAet,
+                                         const std::string& calledAet,
+                                         IApplicationEntityFilter* filter) :
+      server_(server),
+      assoc_(assoc),
+      remoteIp_(remoteIp),
+      remoteAet_(remoteAet),
+      calledAet_(calledAet),
+      filter_(filter)
+    {
+      associationTimeout_ = server.GetAssociationTimeout();
+      elapsedTimeSinceLastCommand_ = 0;
+    }
+
+
+    CommandDispatcher::~CommandDispatcher()
+    {
+      try
+      {
+        AssociationCleanup(assoc_);
+      }
+      catch (...)
+      {
+        LOG(ERROR) << "Some association was not cleanly aborted";
+      }
+    }
+
+
+    bool CommandDispatcher::Step()
+    /*
+     * This function receives DIMSE commmands over the network connection
+     * and handles these commands correspondingly. Note that in case of
+     * storscp only C-ECHO-RQ and C-STORE-RQ commands can be processed.
+     */
+    {
+      bool finished = false;
+
+      // receive a DIMSE command over the network, with a timeout of 1 second
+      DcmDataset *statusDetail = NULL;
+      T_ASC_PresentationContextID presID = 0;
+      T_DIMSE_Message msg;
+
+      OFCondition cond = DIMSE_receiveCommand(assoc_, DIMSE_NONBLOCKING, 1, &presID, &msg, &statusDetail);
+      elapsedTimeSinceLastCommand_++;
+    
+      // if the command which was received has extra status
+      // detail information, dump this information
+      if (statusDetail != NULL)
+      {
+        //LOG4CPP_WARN(Internals::GetLogger(), "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
+        delete statusDetail;
+      }
+
+      if (cond == DIMSE_OUTOFRESOURCES)
+      {
+        finished = true;
+      }
+      else if (cond == DIMSE_NODATAAVAILABLE)
+      {
+        // Timeout due to DIMSE_NONBLOCKING
+        if (associationTimeout_ != 0 && 
+            elapsedTimeSinceLastCommand_ >= associationTimeout_)
+        {
+          // This timeout is actually a association timeout
+          finished = true;
+        }
+      }
+      else if (cond == EC_Normal)
+      {
+        // Reset the association timeout counter
+        elapsedTimeSinceLastCommand_ = 0;
+
+        // Convert the type of request to Orthanc's internal type
+        bool supported = false;
+        DicomRequestType request;
+        switch (msg.CommandField)
+        {
+          case DIMSE_C_ECHO_RQ:
+            request = DicomRequestType_Echo;
+            supported = true;
+            break;
+
+          case DIMSE_C_STORE_RQ:
+            request = DicomRequestType_Store;
+            supported = true;
+            break;
+
+          case DIMSE_C_MOVE_RQ:
+            request = DicomRequestType_Move;
+            supported = true;
+            break;
+
+          case DIMSE_C_FIND_RQ:
+            request = DicomRequestType_Find;
+            supported = true;
+            break;
+
+          default:
+            // we cannot handle this kind of message
+            cond = DIMSE_BADCOMMANDTYPE;
+            LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField;
+            break;
+        }
+
+
+        // Check whether this request is allowed by the security filter
+        if (supported && 
+            filter_ != NULL &&
+            !filter_->IsAllowedRequest(remoteIp_, remoteAet_, calledAet_, request))
+        {
+          LOG(WARNING) << "Rejected " << EnumerationToString(request)
+                       << " request from remote DICOM modality with AET \""
+                       << remoteAet_ << "\" and hostname \"" << remoteIp_ << "\"";
+          cond = DIMSE_ILLEGALASSOCIATION;
+          supported = false;
+          finished = true;
+        }
+
+        // in case we received a supported message, process this command
+        if (supported)
+        {
+          // If anything goes wrong, there will be a "BADCOMMANDTYPE" answer
+          cond = DIMSE_BADCOMMANDTYPE;
+
+          switch (request)
+          {
+            case DicomRequestType_Echo:
+              cond = EchoScp(assoc_, &msg, presID);
+              break;
+
+            case DicomRequestType_Store:
+              if (server_.HasStoreRequestHandlerFactory()) // Should always be true
+              {
+                std::auto_ptr<IStoreRequestHandler> handler
+                  (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler());
+
+                if (handler.get() != NULL)
+                {
+                  cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_);
+                }
+              }
+              break;
+
+            case DicomRequestType_Move:
+              if (server_.HasMoveRequestHandlerFactory()) // Should always be true
+              {
+                std::auto_ptr<IMoveRequestHandler> handler
+                  (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler());
+
+                if (handler.get() != NULL)
+                {
+                  cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_);
+                }
+              }
+              break;
+
+            case DicomRequestType_Find:
+              if (server_.HasFindRequestHandlerFactory() || // Should always be true
+                  server_.HasWorklistRequestHandlerFactory())
+              {
+                std::auto_ptr<IFindRequestHandler> findHandler;
+                if (server_.HasFindRequestHandlerFactory())
+                {
+                  findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler());
+                }
+
+                std::auto_ptr<IWorklistRequestHandler> worklistHandler;
+                if (server_.HasWorklistRequestHandlerFactory())
+                {
+                  worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler());
+                }
+
+                cond = Internals::findScp(assoc_, &msg, presID, server_.GetRemoteModalities(),
+                                          findHandler.get(), worklistHandler.get(),
+                                          remoteIp_, remoteAet_, calledAet_);
+              }
+              break;
+
+            default:
+              // Should never happen
+              break;
+          }
+        }
+      }
+      else
+      {
+        // Bad status, which indicates the closing of the connection by
+        // the peer or a network error
+        finished = true;
+
+        LOG(INFO) << cond.text();
+      }
+    
+      if (finished)
+      {
+        if (cond == DUL_PEERREQUESTEDRELEASE)
+        {
+          LOG(INFO) << "Association Release";
+          ASC_acknowledgeRelease(assoc_);
+        }
+        else if (cond == DUL_PEERABORTEDASSOCIATION)
+        {
+          LOG(INFO) << "Association Aborted";
+        }
+        else
+        {
+          OFString temp_str;
+          LOG(INFO) << "DIMSE failure (aborting association): " << cond.text();
+          /* some kind of error so abort the association */
+          ASC_abortAssociation(assoc_);
+        }
+      }
+
+      return !finished;
+    }
+
+
+    OFCondition EchoScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID)
+    {
+      OFString temp_str;
+      LOG(INFO) << "Received Echo Request";
+      //LOG(DEBUG) << DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID));
+
+      /* the echo succeeded !! */
+      OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL);
+      if (cond.bad())
+      {
+        LOG(ERROR) << "Echo SCP Failed: " << cond.text();
+      }
+      return cond;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/Internals/CommandDispatcher.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,79 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../DicomServer.h"
+#include "../../MultiThreading/IRunnableBySteps.h"
+
+#include <dcmtk/dcmnet/dimse.h>
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    OFCondition AssociationCleanup(T_ASC_Association *assoc);
+
+    class CommandDispatcher : public IRunnableBySteps
+    {
+    private:
+      uint32_t associationTimeout_;
+      uint32_t elapsedTimeSinceLastCommand_;
+      const DicomServer& server_;
+      T_ASC_Association* assoc_;
+      std::string remoteIp_;
+      std::string remoteAet_;
+      std::string calledAet_;
+      IApplicationEntityFilter* filter_;
+
+    public:
+      CommandDispatcher(const DicomServer& server,
+                        T_ASC_Association* assoc,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet,
+                        IApplicationEntityFilter* filter);
+
+      virtual ~CommandDispatcher();
+
+      virtual bool Step();
+    };
+
+    OFCondition EchoScp(T_ASC_Association * assoc, 
+                        T_DIMSE_Message * msg, 
+                        T_ASC_PresentationContextID presID);
+
+    CommandDispatcher* AcceptAssociation(const DicomServer& server, 
+                                         T_ASC_Network *net);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/Internals/FindScp.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,348 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
+
+#include "../../PrecompiledHeaders.h"
+#include "FindScp.h"
+
+#include "../../DicomParsing/FromDcmtkBridge.h"
+#include "../../DicomParsing/ToDcmtkBridge.h"
+#include "../../Logging.h"
+#include "../../OrthancException.h"
+
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+
+
+
+/**
+ * The function below is extracted from DCMTK 3.6.0, cf. file
+ * "dcmtk-3.6.0/dcmwlm/libsrc/wldsfs.cc".
+ **/
+
+static void HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(DcmDataset *dataset, 
+                                                                             const DcmTagKey &sequenceTagKey)
+// Date         : May 3, 2005
+// Author       : Thomas Wilkens
+// Task         : This function performs a check on a sequence attribute in the given dataset. At two different places
+//                in the definition of the DICOM worklist management service, a sequence attribute with a return type
+//                of 2 is mentioned containing two 1C attributes in its item; the condition of the two 1C attributes
+//                specifies that in case a sequence item is present, then these two attributes must be existent and
+//                must contain a value. (I am talking about ReferencedStudySequence and ReferencedPatientSequence.)
+//                In cases where the sequence attribute contains exactly one item with an empty ReferencedSOPClass
+//                and an empty ReferencedSOPInstance, we want to remove the item from the sequence. This is what
+//                this function does.
+// Parameters   : dataset         - [in] Dataset in which the consistency of the sequence attribute shall be checked.
+//                sequenceTagKey  - [in] DcmTagKey of the sequence attribute which shall be checked.
+// Return Value : none.
+{
+  DcmElement *sequenceAttribute = NULL, *referencedSOPClassUIDAttribute = NULL, *referencedSOPInstanceUIDAttribute = NULL;
+
+  // in case the sequence attribute contains exactly one item with an empty
+  // ReferencedSOPClassUID and an empty ReferencedSOPInstanceUID, remove the item
+  if( dataset->findAndGetElement( sequenceTagKey, sequenceAttribute ).good() &&
+      ( (DcmSequenceOfItems*)sequenceAttribute )->card() == 1 &&
+      ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute ).good() &&
+      referencedSOPClassUIDAttribute->getLength() == 0 &&
+      ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse ).good() &&
+      referencedSOPInstanceUIDAttribute->getLength() == 0 )
+  {
+    DcmItem *item = ((DcmSequenceOfItems*)sequenceAttribute)->remove( ((DcmSequenceOfItems*)sequenceAttribute)->getItem(0) );
+    delete item;
+  }
+}
+
+
+
+namespace Orthanc
+{
+  namespace
+  {  
+    struct FindScpData
+    {
+      DicomServer::IRemoteModalities* modalities_;
+      IFindRequestHandler* findHandler_;
+      IWorklistRequestHandler* worklistHandler_;
+      DicomFindAnswers answers_;
+      DcmDataset* lastRequest_;
+      const std::string* remoteIp_;
+      const std::string* remoteAet_;
+      const std::string* calledAet_;
+
+      FindScpData() : answers_(false)
+      {
+      }
+    };
+
+
+
+    static void FixWorklistQuery(ParsedDicomFile& query)
+    {
+      // TODO: Check out
+      // WlmDataSourceFileSystem::HandleExistentButEmptyDescriptionAndCodeSequenceAttributes()"
+      // in DCMTK 3.6.0
+
+      DcmDataset* dataset = query.GetDcmtkObject().getDataset();      
+      HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedStudySequence);
+      HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedPatientSequence);
+    }
+
+
+
+    void FindScpCallback(
+      /* in */ 
+      void *callbackData,  
+      OFBool cancelled, 
+      T_DIMSE_C_FindRQ *request, 
+      DcmDataset *requestIdentifiers, 
+      int responseCount,
+      /* out */
+      T_DIMSE_C_FindRSP *response,
+      DcmDataset **responseIdentifiers,
+      DcmDataset **statusDetail)
+    {
+      bzero(response, sizeof(T_DIMSE_C_FindRSP));
+      *statusDetail = NULL;
+
+      std::string sopClassUid(request->AffectedSOPClassUID);
+
+      FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData);
+      if (data.lastRequest_ == NULL)
+      {
+        bool ok = false;
+
+        try
+        {
+          RemoteModalityParameters modality;
+
+          /**
+           * Ensure that the remote modality is known to Orthanc for C-FIND requests.
+           **/
+
+          assert(data.modalities_ != NULL);
+          if (!data.modalities_->LookupAETitle(modality, *data.remoteAet_))
+          {
+            LOG(ERROR) << "Modality with AET \"" << *data.remoteAet_
+                       << "\" is not defined in the \"DicomModalities\" configuration option";
+            throw OrthancException(ErrorCode_UnknownModality);
+          }
+
+          
+          if (sopClassUid == UID_FINDModalityWorklistInformationModel)
+          {
+            data.answers_.SetWorklist(true);
+
+            if (data.worklistHandler_ != NULL)
+            {
+              ParsedDicomFile query(*requestIdentifiers);
+              FixWorklistQuery(query);
+
+              data.worklistHandler_->Handle(data.answers_, query,
+                                            *data.remoteIp_, *data.remoteAet_,
+                                            *data.calledAet_, modality.GetManufacturer());
+              ok = true;
+            }
+            else
+            {
+              LOG(ERROR) << "No worklist handler is installed, cannot handle this C-FIND request";
+            }
+          }
+          else
+          {
+            data.answers_.SetWorklist(false);
+
+            if (data.findHandler_ != NULL)
+            {
+              std::list<DicomTag> sequencesToReturn;
+
+              for (unsigned long i = 0; i < requestIdentifiers->card(); i++)
+              {
+                DcmElement* element = requestIdentifiers->getElement(i);
+                if (element && !element->isLeaf())
+                {
+                  const DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
+
+                  DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
+                  if (sequence.card() != 0)
+                  {
+                    LOG(WARNING) << "Orthanc only supports sequence matching on worklists, "
+                                 << "ignoring C-FIND SCU constraint on tag (" << tag.Format() 
+                                 << ") " << FromDcmtkBridge::GetTagName(*element);
+                  }
+
+                  sequencesToReturn.push_back(tag);
+                }
+              }
+
+              DicomMap input;
+              FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers);
+
+              data.findHandler_->Handle(data.answers_, input, sequencesToReturn,
+                                        *data.remoteIp_, *data.remoteAet_,
+                                        *data.calledAet_, modality.GetManufacturer());
+              ok = true;
+            }
+            else
+            {
+              LOG(ERROR) << "No C-Find handler is installed, cannot handle this request";
+            }
+          }
+        }
+        catch (OrthancException& e)
+        {
+          // Internal error!
+          LOG(ERROR) <<  "C-FIND request handler has failed: " << e.What();
+        }
+
+        if (!ok)
+        {
+          response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
+          *responseIdentifiers = NULL;   
+          return;
+        }
+
+        data.lastRequest_ = requestIdentifiers;
+      }
+      else if (data.lastRequest_ != requestIdentifiers)
+      {
+        // Internal error!
+        response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
+        *responseIdentifiers = NULL;   
+        return;
+      }
+
+      if (responseCount <= static_cast<int>(data.answers_.GetSize()))
+      {
+        // There are pending results that are still to be sent
+        response->DimseStatus = STATUS_Pending;
+        *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1);
+      }
+      else if (data.answers_.IsComplete())
+      {
+        // Success: All the results have been sent
+        response->DimseStatus = STATUS_Success;
+        *responseIdentifiers = NULL;
+      }
+      else
+      {
+        // Success, but the results were too numerous and had to be cropped
+        LOG(WARNING) <<  "Too many results for an incoming C-FIND query";
+        response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest;
+        *responseIdentifiers = NULL;
+      }
+    }
+  }
+
+
+  OFCondition Internals::findScp(T_ASC_Association * assoc, 
+                                 T_DIMSE_Message * msg, 
+                                 T_ASC_PresentationContextID presID,
+                                 DicomServer::IRemoteModalities& modalities,
+                                 IFindRequestHandler* findHandler,
+                                 IWorklistRequestHandler* worklistHandler,
+                                 const std::string& remoteIp,
+                                 const std::string& remoteAet,
+                                 const std::string& calledAet)
+  {
+    FindScpData data;
+    data.modalities_ = &modalities;
+    data.findHandler_ = findHandler;
+    data.worklistHandler_ = worklistHandler;
+    data.lastRequest_ = NULL;
+    data.remoteIp_ = &remoteIp;
+    data.remoteAet_ = &remoteAet;
+    data.calledAet_ = &calledAet;
+
+    OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, 
+                                          FindScpCallback, &data,
+                                          /*opt_blockMode*/ DIMSE_BLOCKING, 
+                                          /*opt_dimse_timeout*/ 0);
+
+    // if some error occured, dump corresponding information and remove the outfile if necessary
+    if (cond.bad())
+    {
+      OFString temp_str;
+      LOG(ERROR) << "Find SCP Failed: " << cond.text();
+    }
+
+    return cond;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/Internals/FindScp.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../DicomServer.h"
+
+#include <dcmtk/dcmnet/dimse.h>
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    OFCondition findScp(T_ASC_Association * assoc, 
+                        T_DIMSE_Message * msg, 
+                        T_ASC_PresentationContextID presID,
+                        DicomServer::IRemoteModalities& modalities,
+                        IFindRequestHandler* findHandler,   // can be NULL
+                        IWorklistRequestHandler* worklistHandler,   // can be NULL
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/Internals/MoveScp.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,283 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
+#include "../../PrecompiledHeaders.h"
+#include "MoveScp.h"
+
+#include <memory>
+
+#include "../../DicomParsing/FromDcmtkBridge.h"
+#include "../../DicomParsing/ToDcmtkBridge.h"
+#include "../../Logging.h"
+#include "../../OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+
+
+namespace Orthanc
+{
+  namespace
+  {  
+    struct MoveScpData
+    {
+      std::string target_;
+      IMoveRequestHandler* handler_;
+      DcmDataset* lastRequest_;
+      unsigned int subOperationCount_;
+      unsigned int failureCount_;
+      unsigned int warningCount_;
+      std::auto_ptr<IMoveRequestIterator> iterator_;
+      const std::string* remoteIp_;
+      const std::string* remoteAet_;
+      const std::string* calledAet_;
+    };
+
+
+
+    static uint16_t GetMessageId(const DicomMap& message)
+    {
+      /**
+       * Retrieve the Message ID (0000,0110) for this C-MOVE request, if
+       * any. If present, this Message ID will be stored in the Move
+       * Originator Message ID (0000,1031) field of the C-MOVE response.
+       * http://dicom.nema.org/dicom/2013/output/chtml/part07/chapter_E.html
+       **/
+
+      const DicomValue* value = message.TestAndGetValue(DICOM_TAG_MESSAGE_ID);
+
+      if (value != NULL &&
+          !value->IsNull() &&
+          !value->IsBinary())
+      {
+        try
+        {
+          int tmp = boost::lexical_cast<int>(value->GetContent());
+          if (tmp >= 0 && tmp <= 0xffff)
+          {
+            return static_cast<uint16_t>(tmp);
+          }
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          LOG(WARNING) << "Cannot convert the Message ID (\"" << value->GetContent()
+                       << "\") of an incoming C-MOVE request to an integer, assuming zero";
+        }
+      }
+
+      return 0;
+    }
+
+
+
+    void MoveScpCallback(
+      /* in */ 
+      void *callbackData,  
+      OFBool cancelled, 
+      T_DIMSE_C_MoveRQ *request, 
+      DcmDataset *requestIdentifiers, 
+      int responseCount,
+      /* out */
+      T_DIMSE_C_MoveRSP *response,
+      DcmDataset **responseIdentifiers,
+      DcmDataset **statusDetail)
+    {
+      bzero(response, sizeof(T_DIMSE_C_MoveRSP));
+      *statusDetail = NULL;
+      *responseIdentifiers = NULL;   
+
+      MoveScpData& data = *reinterpret_cast<MoveScpData*>(callbackData);
+      if (data.lastRequest_ == NULL)
+      {
+        DicomMap input;
+        FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers);
+
+        try
+        {
+          data.iterator_.reset(data.handler_->Handle(data.target_, input, *data.remoteIp_, *data.remoteAet_,
+                                                     *data.calledAet_, GetMessageId(input)));
+
+          if (data.iterator_.get() == NULL)
+          {
+            // Internal error!
+            response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess;
+            return;
+          }
+
+          data.subOperationCount_ = data.iterator_->GetSubOperationCount();
+          data.failureCount_ = 0;
+          data.warningCount_ = 0;
+        }
+        catch (OrthancException& e)
+        {
+          // Internal error!
+          LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What();
+          response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess;
+          return;
+        }
+
+        data.lastRequest_ = requestIdentifiers;
+      }
+      else if (data.lastRequest_ != requestIdentifiers)
+      {
+        // Internal error!
+        response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess;
+        return;
+      }
+  
+      if (data.subOperationCount_ == 0)
+      {
+        response->DimseStatus = STATUS_Success;
+      }
+      else
+      {
+        IMoveRequestIterator::Status status;
+
+        try
+        {
+          status = data.iterator_->DoNext();
+        }
+        catch (OrthancException& e)
+        {
+          // Internal error!
+          LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What();
+          response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess;
+          return;
+        }
+
+        if (status == IMoveRequestIterator::Status_Failure)
+        {
+          data.failureCount_++;
+        }
+        else if (status == IMoveRequestIterator::Status_Warning)
+        {
+          data.warningCount_++;
+        }
+
+        if (responseCount < static_cast<int>(data.subOperationCount_))
+        {
+          response->DimseStatus = STATUS_Pending;
+        }
+        else
+        {
+          response->DimseStatus = STATUS_Success;
+        }
+      }
+
+      response->NumberOfRemainingSubOperations = data.subOperationCount_ - responseCount;
+      response->NumberOfCompletedSubOperations = responseCount;
+      response->NumberOfFailedSubOperations = data.failureCount_;
+      response->NumberOfWarningSubOperations = data.warningCount_;
+    }
+  }
+
+
+  OFCondition Internals::moveScp(T_ASC_Association * assoc, 
+                                 T_DIMSE_Message * msg, 
+                                 T_ASC_PresentationContextID presID,
+                                 IMoveRequestHandler& handler,
+                                 const std::string& remoteIp,
+                                 const std::string& remoteAet,
+                                 const std::string& calledAet)
+  {
+    MoveScpData data;
+    data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination);
+    data.lastRequest_ = NULL;
+    data.handler_ = &handler;
+    data.remoteIp_ = &remoteIp;
+    data.remoteAet_ = &remoteAet;
+    data.calledAet_ = &calledAet;
+
+    OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, 
+                                          MoveScpCallback, &data,
+                                          /*opt_blockMode*/ DIMSE_BLOCKING, 
+                                          /*opt_dimse_timeout*/ 0);
+
+    // if some error occured, dump corresponding information and remove the outfile if necessary
+    if (cond.bad())
+    {
+      OFString temp_str;
+      LOG(ERROR) << "Move SCP Failed: " << cond.text();
+    }
+
+    return cond;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/Internals/MoveScp.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../IMoveRequestHandler.h"
+
+#include <dcmtk/dcmnet/dimse.h>
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    OFCondition moveScp(T_ASC_Association * assoc, 
+                        T_DIMSE_Message * msg, 
+                        T_ASC_PresentationContextID presID,
+                        IMoveRequestHandler& handler,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/Internals/StoreScp.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,298 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
+#include "../../PrecompiledHeaders.h"
+#include "StoreScp.h"
+
+#include "../../DicomParsing/FromDcmtkBridge.h"
+#include "../../DicomParsing/ToDcmtkBridge.h"
+#include "../../OrthancException.h"
+#include "../../Logging.h"
+
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcmetinf.h>
+#include <dcmtk/dcmdata/dcostrmb.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmnet/diutil.h>
+
+
+namespace Orthanc
+{
+  namespace
+  {  
+    struct StoreCallbackData
+    {
+      IStoreRequestHandler* handler;
+      const std::string* remoteIp;
+      const char* remoteAET;
+      const char* calledAET;
+      const char* modality;
+      const char* affectedSOPInstanceUID;
+      uint32_t messageID;
+    };
+
+    
+    static void
+    storeScpCallback(
+      void *callbackData,
+      T_DIMSE_StoreProgress *progress,
+      T_DIMSE_C_StoreRQ *req,
+      char * /*imageFileName*/, DcmDataset **imageDataSet,
+      T_DIMSE_C_StoreRSP *rsp,
+      DcmDataset **statusDetail)
+    /*
+     * This function.is used to indicate progress when storescp receives instance data over the
+     * network. On the final call to this function (identified by progress->state == DIMSE_StoreEnd)
+     * this function will store the data set which was received over the network to a file.
+     * Earlier calls to this function will simply cause some information to be dumped to stdout.
+     *
+     * Parameters:
+     *   callbackData  - [in] data for this callback function
+     *   progress      - [in] The state of progress. (identifies if this is the initial or final call
+     *                   to this function, or a call in between these two calls.
+     *   req           - [in] The original store request message.
+     *   imageFileName - [in] The path to and name of the file the information shall be written to.
+     *   imageDataSet  - [in] The data set which shall be stored in the image file
+     *   rsp           - [inout] the C-STORE-RSP message (will be sent after the call to this function)
+     *   statusDetail  - [inout] This variable can be used to capture detailed information with regard to
+     *                   the status information which is captured in the status element (0000,0900). Note
+     *                   that this function does specify any such information, the pointer will be set to NULL.
+     */
+    {
+      StoreCallbackData *cbdata = OFstatic_cast(StoreCallbackData *, callbackData);
+
+      DIC_UI sopClass;
+      DIC_UI sopInstance;
+
+      // if this is the final call of this function, save the data which was received to a file
+      // (note that we could also save the image somewhere else, put it in database, etc.)
+      if (progress->state == DIMSE_StoreEnd)
+      {
+        OFString tmpStr;
+
+        // do not send status detail information
+        *statusDetail = NULL;
+
+        // Concerning the following line: an appropriate status code is already set in the resp structure,
+        // it need not be success. For example, if the caller has already detected an out of resources problem
+        // then the status will reflect this.  The callback function is still called to allow cleanup.
+        //rsp->DimseStatus = STATUS_Success;
+
+        // we want to write the received information to a file only if this information
+        // is present and the options opt_bitPreserving and opt_ignore are not set.
+        if ((imageDataSet != NULL) && (*imageDataSet != NULL))
+        {
+          DicomMap summary;
+          Json::Value dicomJson;
+          std::string buffer;
+
+          try
+          {
+            FromDcmtkBridge::ExtractDicomSummary(summary, **imageDataSet);
+            FromDcmtkBridge::ExtractDicomAsJson(dicomJson, **imageDataSet);
+
+            if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet))
+            {
+              LOG(ERROR) << "cannot write DICOM file to memory";
+              rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
+            }
+          }
+          catch (...)
+          {
+            rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
+          }
+
+          // check the image to make sure it is consistent, i.e. that its sopClass and sopInstance correspond
+          // to those mentioned in the request. If not, set the status in the response message variable.
+          if (rsp->DimseStatus == STATUS_Success)
+          {
+            // which SOP class and SOP instance ?
+            if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sopInstance, /*opt_correctUIDPadding*/ OFFalse))
+            {
+              //LOG4CPP_ERROR(Internals::GetLogger(), "bad DICOM file: " << fileName);
+              rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand;
+            }
+            else if (strcmp(sopClass, req->AffectedSOPClassUID) != 0)
+            {
+              rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
+            }
+            else if (strcmp(sopInstance, req->AffectedSOPInstanceUID) != 0)
+            {
+              rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
+            }
+            else
+            {
+              try
+              {
+                cbdata->handler->Handle(buffer, summary, dicomJson, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET);
+              }
+              catch (OrthancException& e)
+              {
+                rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
+
+                if (e.GetErrorCode() == ErrorCode_InexistentTag)
+                {
+                  summary.LogMissingTagsForStore();
+                }
+                else
+                {
+                  LOG(ERROR) << "Exception while storing DICOM: " << e.What();
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+/*
+ * This function processes a DIMSE C-STORE-RQ commmand that was
+ * received over the network connection.
+ *
+ * Parameters:
+ *   assoc  - [in] The association (network connection to another DICOM application).
+ *   msg    - [in] The DIMSE C-STORE-RQ message that was received.
+ *   presID - [in] The ID of the presentation context which was specified in the PDV which contained
+ *                 the DIMSE command.
+ */
+  OFCondition Internals::storeScp(T_ASC_Association * assoc, 
+                                  T_DIMSE_Message * msg, 
+                                  T_ASC_PresentationContextID presID,
+                                  IStoreRequestHandler& handler,
+                                  const std::string& remoteIp)
+  {
+    OFCondition cond = EC_Normal;
+    T_DIMSE_C_StoreRQ *req;
+
+    // assign the actual information of the C-STORE-RQ command to a local variable
+    req = &msg->msg.CStoreRQ;
+
+    // intialize some variables
+    StoreCallbackData data;
+    data.handler = &handler;
+    data.remoteIp = &remoteIp;
+    data.modality = dcmSOPClassUIDToModality(req->AffectedSOPClassUID/*, "UNKNOWN"*/);
+    if (data.modality == NULL)
+      data.modality = "UNKNOWN";
+
+    data.affectedSOPInstanceUID = req->AffectedSOPInstanceUID;
+    data.messageID = req->MessageID;
+    if (assoc && assoc->params)
+    {
+      data.remoteAET = assoc->params->DULparams.callingAPTitle;
+      data.calledAET = assoc->params->DULparams.calledAPTitle;
+    }
+    else
+    {
+      data.remoteAET = "";
+      data.calledAET = "";
+    }
+
+    DcmFileFormat dcmff;
+
+    // store SourceApplicationEntityTitle in metaheader
+    if (assoc && assoc->params)
+    {
+      const char *aet = assoc->params->DULparams.callingAPTitle;
+      if (aet) dcmff.getMetaInfo()->putAndInsertString(DCM_SourceApplicationEntityTitle, aet);
+    }
+
+    // define an address where the information which will be received over the network will be stored
+    DcmDataset *dset = dcmff.getDataset();
+
+    cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset,
+                               storeScpCallback, &data, 
+                               /*opt_blockMode*/ DIMSE_BLOCKING, 
+                               /*opt_dimse_timeout*/ 0);
+
+    // if some error occured, dump corresponding information and remove the outfile if necessary
+    if (cond.bad())
+    {
+      OFString temp_str;
+      LOG(ERROR) << "Store SCP Failed: " << cond.text();
+    }
+
+    // return return value
+    return cond;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/Internals/StoreScp.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,50 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../IStoreRequestHandler.h"
+
+#include <dcmtk/dcmnet/dimse.h>
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    OFCondition storeScp(T_ASC_Association * assoc, 
+                         T_DIMSE_Message * msg, 
+                         T_ASC_PresentationContextID presID,
+                         IStoreRequestHandler& handler,
+                         const std::string& remoteIp);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/RemoteModalityParameters.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,128 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "RemoteModalityParameters.h"
+
+#include "../Logging.h"
+#include "../OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+#include <stdexcept>
+
+namespace Orthanc
+{
+  RemoteModalityParameters::RemoteModalityParameters() :
+    aet_("ORTHANC"),
+    host_("127.0.0.1"),
+    port_(104),
+    manufacturer_(ModalityManufacturer_Generic)
+  {
+  }
+
+  RemoteModalityParameters::RemoteModalityParameters(const std::string& aet,
+                                                     const std::string& host,
+                                                     uint16_t port,
+                                                     ModalityManufacturer manufacturer)
+  {
+    SetApplicationEntityTitle(aet);
+    SetHost(host);
+    SetPort(port);
+    SetManufacturer(manufacturer);
+  }
+
+
+  void RemoteModalityParameters::FromJson(const Json::Value& modality)
+  {
+    if (!modality.isArray() ||
+        (modality.size() != 3 && modality.size() != 4))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    SetApplicationEntityTitle(modality.get(0u, "").asString());
+    SetHost(modality.get(1u, "").asString());
+
+    const Json::Value& portValue = modality.get(2u, "");
+    try
+    {
+      int tmp = portValue.asInt();
+
+      if (tmp <= 0 || tmp >= 65535)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      SetPort(static_cast<uint16_t>(tmp));
+    }
+    catch (std::runtime_error /* error inside JsonCpp */)
+    {
+      try
+      {
+        SetPort(boost::lexical_cast<uint16_t>(portValue.asString()));
+      }
+      catch (boost::bad_lexical_cast)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+
+    if (modality.size() == 4)
+    {
+      const std::string& manufacturer = modality.get(3u, "").asString();
+
+      try
+      {
+        SetManufacturer(manufacturer);
+      }
+      catch (OrthancException&)
+      {
+        LOG(ERROR) << "Unknown modality manufacturer: \"" << manufacturer << "\"";
+        throw;
+      }
+    }
+    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/Core/DicomNetworking/RemoteModalityParameters.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,109 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+
+#include <stdint.h>
+#include <string>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class RemoteModalityParameters
+  {
+  private:
+    std::string aet_;
+    std::string host_;
+    uint16_t port_;
+    ModalityManufacturer manufacturer_;
+
+  public:
+    RemoteModalityParameters();
+
+    RemoteModalityParameters(const std::string& aet,
+                             const std::string& host,
+                             uint16_t port,
+                             ModalityManufacturer manufacturer);
+
+    const std::string& GetApplicationEntityTitle() const
+    {
+      return aet_;
+    }
+
+    void SetApplicationEntityTitle(const std::string& aet)
+    {
+      aet_ = aet;
+    }
+
+    const std::string& GetHost() const
+    {
+      return host_;
+    }
+
+    void SetHost(const std::string& host)
+    {
+      host_ = host;
+    }
+    
+    uint16_t GetPort() const
+    {
+      return port_;
+    }
+
+    void SetPort(uint16_t port)
+    {
+      port_ = port;
+    }
+
+    ModalityManufacturer GetManufacturer() const
+    {
+      return manufacturer_;
+    }
+
+    void SetManufacturer(ModalityManufacturer manufacturer)
+    {
+      manufacturer_ = manufacturer;
+    }    
+
+    void SetManufacturer(const std::string& manufacturer)
+    {
+      manufacturer_ = StringToModalityManufacturer(manufacturer);
+    }
+
+    void FromJson(const Json::Value& modality);
+
+    void ToJson(Json::Value& value) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/ReusableDicomUserConnection.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,188 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "ReusableDicomUserConnection.h"
+
+#include "../Logging.h"
+#include "../OrthancException.h"
+
+namespace Orthanc
+{
+  static boost::posix_time::ptime Now()
+  {
+    return boost::posix_time::microsec_clock::local_time();
+  }
+
+  void ReusableDicomUserConnection::Open(const std::string& localAet,
+                                         const RemoteModalityParameters& remote)
+  {
+    if (connection_ != NULL &&
+        connection_->GetLocalApplicationEntityTitle() == localAet &&
+        connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() &&
+        connection_->GetRemoteHost() == remote.GetHost() &&
+        connection_->GetRemotePort() == remote.GetPort() &&
+        connection_->GetRemoteManufacturer() == remote.GetManufacturer())
+    {
+      // The current connection can be reused
+      LOG(INFO) << "Reusing the previous SCU connection";
+      return;
+    }
+
+    Close();
+
+    connection_ = new DicomUserConnection();
+    connection_->SetLocalApplicationEntityTitle(localAet);
+    connection_->SetRemoteModality(remote);
+    connection_->Open();
+  }
+    
+  void ReusableDicomUserConnection::Close()
+  {
+    if (connection_ != NULL)
+    {
+      delete connection_;
+      connection_ = NULL;
+    }
+  }
+
+  void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that)
+  {
+    for (;;)
+    {
+      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+      if (!that->continue_)
+      {
+        //LOG(INFO) << "Finishing the thread watching the global SCU connection";
+        return;
+      }
+
+      {
+        boost::mutex::scoped_lock lock(that->mutex_);
+        if (that->connection_ != NULL &&
+            Now() >= that->lastUse_ + that->timeBeforeClose_)
+        {
+          LOG(INFO) << "Closing the global SCU connection after timeout";
+          that->Close();
+        }
+      }
+    }
+  }
+    
+
+  ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that,
+                                              const std::string& localAet,
+                                              const RemoteModalityParameters& remote) :
+    ::Orthanc::Locker(that)
+  {
+    that.Open(localAet, remote);
+    connection_ = that.connection_;    
+  }
+
+
+  DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection()
+  {
+    if (connection_ == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return *connection_;
+  }      
+
+  ReusableDicomUserConnection::ReusableDicomUserConnection() : 
+    connection_(NULL), 
+    timeBeforeClose_(boost::posix_time::seconds(5))  // By default, close connection after 5 seconds
+  {
+    lastUse_ = Now();
+    continue_ = true;
+    closeThread_ = boost::thread(CloseThread, this);
+  }
+
+  ReusableDicomUserConnection::~ReusableDicomUserConnection()
+  {
+    if (continue_)
+    {
+      LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!";
+      Finalize();
+    }
+  }
+
+  void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (ms == 0)
+    {
+      ms = 1;
+    }
+
+    timeBeforeClose_ = boost::posix_time::milliseconds(ms);
+  }
+
+  void ReusableDicomUserConnection::Lock()
+  {
+    mutex_.lock();
+  }
+
+  void ReusableDicomUserConnection::Unlock()
+  {
+    if (connection_ != NULL &&
+        connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp)
+    {
+      // "storescp" from DCMTK has problems when reusing a
+      // connection. Always close.
+      Close();
+    }
+
+    lastUse_ = Now();
+    mutex_.unlock();
+  }
+
+  
+  void ReusableDicomUserConnection::Finalize()
+  {
+    if (continue_)
+    {
+      continue_ = false;
+
+      if (closeThread_.joinable())
+      {
+        closeThread_.join();
+      }
+
+      Close();
+    }
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/ReusableDicomUserConnection.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,89 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomUserConnection.h"
+#include "../../Core/MultiThreading/Locker.h"
+
+#include <boost/thread.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace Orthanc
+{
+  class ReusableDicomUserConnection : public ILockable
+  {
+  private:
+    boost::mutex mutex_;
+    DicomUserConnection* connection_;
+    bool continue_;
+    boost::posix_time::time_duration timeBeforeClose_;
+    boost::posix_time::ptime lastUse_;
+    boost::thread closeThread_;
+
+    void Open(const std::string& localAet,
+              const RemoteModalityParameters& remote);
+    
+    void Close();
+
+    static void CloseThread(ReusableDicomUserConnection* that);
+
+  protected:
+    virtual void Lock();
+
+    virtual void Unlock();
+    
+  public:
+    class Locker : public ::Orthanc::Locker
+    {
+    private:
+      DicomUserConnection* connection_;
+
+    public:
+      Locker(ReusableDicomUserConnection& that,
+             const std::string& localAet,
+             const RemoteModalityParameters& remote);
+
+      DicomUserConnection& GetConnection();
+    };
+
+    ReusableDicomUserConnection();
+
+    virtual ~ReusableDicomUserConnection();
+
+    void SetMillisecondsBeforeClose(uint64_t ms);
+
+    void Finalize();
+  };
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DicomDirWriter.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,510 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
+
+/***
+    
+    Validation:
+
+    # sudo apt-get install dicom3tools
+    # dciodvfy DICOMDIR 2>&1 | less
+    # dcentvfy DICOMDIR 2>&1 | less
+
+    http://www.dclunie.com/dicom3tools/dciodvfy.html
+
+    DICOMDIR viewer working with Wine under Linux:
+    http://www.microdicom.com/
+
+ ***/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomDirWriter.h"
+
+#include "FromDcmtkBridge.h"
+#include "ToDcmtkBridge.h"
+
+#include "../Logging.h"
+#include "../OrthancException.h"
+#include "../TemporaryFile.h"
+#include "../Toolbox.h"
+#include "../SystemToolbox.h"
+
+#include <dcmtk/dcmdata/dcdicdir.h>
+#include <dcmtk/dcmdata/dcmetinf.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcuid.h>
+#include <dcmtk/dcmdata/dcddirif.h>
+#include <dcmtk/dcmdata/dcvrui.h>
+#include <dcmtk/dcmdata/dcsequen.h>
+#include <dcmtk/dcmdata/dcostrmf.h>
+#include "dcmtk/dcmdata/dcvrda.h"     /* for class DcmDate */
+#include "dcmtk/dcmdata/dcvrtm.h"     /* for class DcmTime */
+
+#include <memory>
+
+namespace Orthanc
+{
+  class DicomDirWriter::PImpl
+  {
+  private:
+    std::string fileSetId_;
+    TemporaryFile file_;
+    std::auto_ptr<DcmDicomDir> dir_;
+
+    typedef std::pair<ResourceType, std::string>  IndexKey;
+    typedef std::map<IndexKey, DcmDirectoryRecord* >  Index;
+    Index  index_;
+
+
+    DcmDicomDir& GetDicomDir()
+    {
+      if (dir_.get() == NULL)
+      {
+        dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), 
+                                   fileSetId_.c_str()));
+        //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8));
+      }
+
+      return *dir_;
+    }
+
+
+    DcmDirectoryRecord& GetRoot()
+    {
+      return GetDicomDir().getRootRecord();
+    }
+
+
+    static bool GetUtf8TagValue(std::string& result,
+                                DcmItem& source,
+                                Encoding encoding,
+                                const DcmTagKey& key)
+    {
+      DcmElement* element = NULL;
+
+      if (source.findAndGetElement(key, element).good())
+      {
+        char* s = NULL;
+        if (element->isLeaf() &&
+            element->getString(s).good() &&
+            s != NULL)
+        {
+          result = Toolbox::ConvertToUtf8(s, encoding);
+          return true;
+        }
+      }
+
+      result.clear();
+      return false;
+    }
+
+
+    static void SetTagValue(DcmDirectoryRecord& target,
+                            const DcmTagKey& key,
+                            const std::string& valueUtf8)
+    {
+      std::string s = Toolbox::ConvertFromUtf8(valueUtf8, Encoding_Ascii);
+
+      if (!target.putAndInsertString(key, s.c_str()).good())
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+                            
+
+
+    static bool CopyString(DcmDirectoryRecord& target,
+                           DcmDataset& source,
+                           Encoding encoding,
+                           const DcmTagKey& key,
+                           bool optional,
+                           bool copyEmpty)
+    {
+      if (optional &&
+          !source.tagExistsWithValue(key) &&
+          !(copyEmpty && source.tagExists(key)))
+      {
+        return false;
+      }
+
+      std::string value;
+      bool found = GetUtf8TagValue(value, source, encoding, key);
+
+      SetTagValue(target, key, value);
+      return found;
+    }
+
+
+    static void CopyStringType1(DcmDirectoryRecord& target,
+                                DcmDataset& source,
+                                Encoding encoding,
+                                const DcmTagKey& key)
+    {
+      CopyString(target, source, encoding, key, false, false);
+    }
+
+    static void CopyStringType1C(DcmDirectoryRecord& target,
+                                 DcmDataset& source,
+                                 Encoding encoding,
+                                 const DcmTagKey& key)
+    {
+      CopyString(target, source, encoding, key, true, false);
+    }
+
+    static void CopyStringType2(DcmDirectoryRecord& target,
+                                DcmDataset& source,
+                                Encoding encoding,
+                                const DcmTagKey& key)
+    {
+      CopyString(target, source, encoding, key, false, true);
+    }
+
+
+  public:
+    PImpl() : fileSetId_("ORTHANC_MEDIA")
+    {
+    }
+
+    void FillPatient(DcmDirectoryRecord& record,
+                     DcmDataset& dicom,
+                     Encoding encoding)
+    {
+      // cf. "DicomDirInterface::buildPatientRecord()"
+
+      CopyStringType1C(record, dicom, encoding, DCM_PatientID);
+      CopyStringType2(record, dicom, encoding, DCM_PatientName);
+    }
+
+    void FillStudy(DcmDirectoryRecord& record,
+                   DcmDataset& dicom,
+                   Encoding encoding)
+    {
+      // cf. "DicomDirInterface::buildStudyRecord()"
+
+      std::string nowDate, nowTime;
+      SystemToolbox::GetNowDicom(nowDate, nowTime);
+
+      std::string studyDate;
+      if (!GetUtf8TagValue(studyDate, dicom, encoding, DCM_StudyDate) &&
+          !GetUtf8TagValue(studyDate, dicom, encoding, DCM_SeriesDate) &&
+          !GetUtf8TagValue(studyDate, dicom, encoding, DCM_AcquisitionDate) &&
+          !GetUtf8TagValue(studyDate, dicom, encoding, DCM_ContentDate))
+      {
+        studyDate = nowDate;
+      }
+          
+      std::string studyTime;
+      if (!GetUtf8TagValue(studyTime, dicom, encoding, DCM_StudyTime) &&
+          !GetUtf8TagValue(studyTime, dicom, encoding, DCM_SeriesTime) &&
+          !GetUtf8TagValue(studyTime, dicom, encoding, DCM_AcquisitionTime) &&
+          !GetUtf8TagValue(studyTime, dicom, encoding, DCM_ContentTime))
+      {
+        studyTime = nowTime;
+      }
+
+      /* copy attribute values from dataset to study record */
+      SetTagValue(record, DCM_StudyDate, studyDate);
+      SetTagValue(record, DCM_StudyTime, studyTime);
+      CopyStringType2(record, dicom, encoding, DCM_StudyDescription);
+      CopyStringType1(record, dicom, encoding, DCM_StudyInstanceUID);
+      /* use type 1C instead of 1 in order to avoid unwanted overwriting */
+      CopyStringType1C(record, dicom, encoding, DCM_StudyID);
+      CopyStringType2(record, dicom, encoding, DCM_AccessionNumber);
+    }
+
+    void FillSeries(DcmDirectoryRecord& record,
+                    DcmDataset& dicom,
+                    Encoding encoding)
+    {
+      // cf. "DicomDirInterface::buildSeriesRecord()"
+
+      /* copy attribute values from dataset to series record */
+      CopyStringType1(record, dicom, encoding, DCM_Modality);
+      CopyStringType1(record, dicom, encoding, DCM_SeriesInstanceUID);
+      /* use type 1C instead of 1 in order to avoid unwanted overwriting */
+      CopyStringType1C(record, dicom, encoding, DCM_SeriesNumber);
+    }
+
+    void FillInstance(DcmDirectoryRecord& record,
+                      DcmDataset& dicom,
+                      Encoding encoding,
+                      DcmMetaInfo& metaInfo,
+                      const char* path)
+    {
+      // cf. "DicomDirInterface::buildImageRecord()"
+
+      /* copy attribute values from dataset to image record */
+      CopyStringType1(record, dicom, encoding, DCM_InstanceNumber);
+      //CopyElementType1C(record, dicom, encoding, DCM_ImageType);
+
+      // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record);
+
+      std::string sopClassUid, sopInstanceUid, transferSyntaxUid;
+      if (!GetUtf8TagValue(sopClassUid, dicom, encoding, DCM_SOPClassUID) ||
+          !GetUtf8TagValue(sopInstanceUid, dicom, encoding, DCM_SOPInstanceUID) ||
+          !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, DCM_TransferSyntaxUID))
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      SetTagValue(record, DCM_ReferencedFileID, path);
+      SetTagValue(record, DCM_ReferencedSOPClassUIDInFile, sopClassUid);
+      SetTagValue(record, DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUid);
+      SetTagValue(record, DCM_ReferencedTransferSyntaxUIDInFile, transferSyntaxUid);
+    }
+
+    
+
+    bool CreateResource(DcmDirectoryRecord*& target,
+                        ResourceType level,
+                        ParsedDicomFile& dicom,
+                        const char* filename,
+                        const char* path)
+    {
+      DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset();
+      Encoding encoding = dicom.GetEncoding();
+
+      bool found;
+      std::string id;
+      E_DirRecType type;
+
+      switch (level)
+      {
+        case ResourceType_Patient:
+          found = GetUtf8TagValue(id, dataset, encoding, DCM_PatientID);
+          type = ERT_Patient;
+          break;
+
+        case ResourceType_Study:
+          found = GetUtf8TagValue(id, dataset, encoding, DCM_StudyInstanceUID);
+          type = ERT_Study;
+          break;
+
+        case ResourceType_Series:
+          found = GetUtf8TagValue(id, dataset, encoding, DCM_SeriesInstanceUID);
+          type = ERT_Series;
+          break;
+
+        case ResourceType_Instance:
+          found = GetUtf8TagValue(id, dataset, encoding, DCM_SOPInstanceUID);
+          type = ERT_Image;
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      if (!found)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      IndexKey key = std::make_pair(level, std::string(id.c_str()));
+      Index::iterator it = index_.find(key);
+
+      if (it != index_.end())
+      {
+        target = it->second;
+        return false; // Already existing
+      }
+
+      std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename));
+
+      switch (level)
+      {
+        case ResourceType_Patient:
+          FillPatient(*record, dataset, encoding);
+          break;
+
+        case ResourceType_Study:
+          FillStudy(*record, dataset, encoding);
+          break;
+
+        case ResourceType_Series:
+          FillSeries(*record, dataset, encoding);
+          break;
+
+        case ResourceType_Instance:
+          FillInstance(*record, dataset, encoding, *dicom.GetDcmtkObject().getMetaInfo(), path);
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      CopyStringType1C(*record, dataset, encoding, DCM_SpecificCharacterSet);
+
+      target = record.get();
+      GetRoot().insertSub(record.release());
+      index_[key] = target;
+
+      return true;   // Newly created
+    }
+
+    void Read(std::string& s)
+    {
+      if (!GetDicomDir().write(DICOMDIR_DEFAULT_TRANSFERSYNTAX, 
+                               EET_UndefinedLength /*encodingType*/, 
+                               EGL_withoutGL /*groupLength*/).good())
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      file_.Read(s);
+    }
+
+    void SetFileSetId(const std::string& id)
+    {
+      dir_.reset(NULL);
+      fileSetId_ = id;
+    }
+  };
+
+
+  DicomDirWriter::DicomDirWriter() : pimpl_(new PImpl)
+  {
+  }
+
+  DicomDirWriter::~DicomDirWriter()
+  {
+    if (pimpl_)
+    {
+      delete pimpl_;
+    }
+  }
+
+  void DicomDirWriter::SetFileSetId(const std::string& id)
+  {
+    pimpl_->SetFileSetId(id);
+  }
+
+  void DicomDirWriter::Add(const std::string& directory,
+                           const std::string& filename,
+                           ParsedDicomFile& dicom)
+  {
+    std::string path;
+    if (directory.empty())
+    {
+      path = filename;
+    }
+    else
+    {
+      if (directory[directory.length() - 1] == '/' ||
+          directory[directory.length() - 1] == '\\')
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      path = directory + '\\' + filename;
+    }
+
+    DcmDirectoryRecord* instance;
+    bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, dicom, filename.c_str(), path.c_str());
+    if (isNewInstance)
+    {
+      DcmDirectoryRecord* series;
+      bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, dicom, filename.c_str(), NULL);
+      series->insertSub(instance);
+
+      if (isNewSeries)
+      {
+        DcmDirectoryRecord* study;
+        bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, dicom, filename.c_str(), NULL);
+        study->insertSub(series);
+  
+        if (isNewStudy)
+        {
+          DcmDirectoryRecord* patient;
+          pimpl_->CreateResource(patient, ResourceType_Patient, dicom, filename.c_str(), NULL);
+          patient->insertSub(study);
+        }
+      }
+    }
+  }
+
+  void DicomDirWriter::Encode(std::string& target)
+  {
+    pimpl_->Read(target);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DicomDirWriter.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,62 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ParsedDicomFile.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class DicomDirWriter : public boost::noncopyable
+  {
+  private:
+    class PImpl;
+    PImpl* pimpl_;
+
+  public:
+    DicomDirWriter();
+
+    ~DicomDirWriter();
+
+    void SetFileSetId(const std::string& id);
+
+    void Add(const std::string& directory,
+             const std::string& filename,
+             ParsedDicomFile& dicom);
+
+    void Encode(std::string& target);
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DicomModification.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,1132 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomModification.h"
+
+#include "../Logging.h"
+#include "../OrthancException.h"
+#include "FromDcmtkBridge.h"
+
+#include <memory>   // For std::auto_ptr
+
+
+static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2008 =
+  "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1";
+
+static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2017c =
+  "Orthanc " ORTHANC_VERSION " - PS 3.15-2017c Table E.1-1 Basic Profile";
+
+namespace Orthanc
+{
+  bool DicomModification::CancelReplacement(const DicomTag& tag)
+  {
+    Replacements::iterator it = replacements_.find(tag);
+    
+    if (it != replacements_.end())
+    {
+      delete it->second;
+      replacements_.erase(it);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void DicomModification::ReplaceInternal(const DicomTag& tag,
+                                          const Json::Value& value)
+  {
+    Replacements::iterator it = replacements_.find(tag);
+
+    if (it != replacements_.end())
+    {
+      delete it->second;
+      it->second = NULL;   // In the case of an exception during the clone
+      it->second = new Json::Value(value);  // Clone
+    }
+    else
+    {
+      replacements_[tag] = new Json::Value(value);  // Clone
+    }
+  }
+
+
+  void DicomModification::ClearReplacements()
+  {
+    for (Replacements::iterator it = replacements_.begin();
+         it != replacements_.end(); ++it)
+    {
+      delete it->second;
+    }
+
+    replacements_.clear();
+  }
+
+
+  void DicomModification::MarkNotOrthancAnonymization()
+  {
+    Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD);
+
+    if (it != replacements_.end() &&
+        (it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2008 ||
+         it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2017c))
+    {
+      delete it->second;
+      replacements_.erase(it);
+    }
+  }
+
+
+  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, false /* don't try and decode data URI scheme for UIDs */, DicomReplaceMode_InsertIfAbsent);
+  }
+  
+  DicomModification::DicomModification() :
+    removePrivateTags_(false),
+    level_(ResourceType_Instance),
+    allowManualIdentifiers_(true),
+    keepStudyInstanceUid_(false),
+    keepSeriesInstanceUid_(false)
+  {
+  }
+
+  DicomModification::~DicomModification()
+  {
+    ClearReplacements();
+  }
+
+  void DicomModification::Keep(const DicomTag& tag)
+  {
+    bool wasRemoved = IsRemoved(tag);
+    bool wasCleared = IsCleared(tag);
+    
+    removals_.erase(tag);
+    clearings_.erase(tag);
+
+    bool wasReplaced = CancelReplacement(tag);
+
+    if (tag == DICOM_TAG_STUDY_INSTANCE_UID)
+    {
+      keepStudyInstanceUid_ = true;
+    }
+    else if (tag == DICOM_TAG_SERIES_INSTANCE_UID)
+    {
+      keepSeriesInstanceUid_ = true;
+    }
+    else if (tag.IsPrivate())
+    {
+      privateTagsToKeep_.insert(tag);
+    }
+    else if (!wasRemoved &&
+             !wasReplaced &&
+             !wasCleared)
+    {
+      LOG(WARNING) << "Marking this tag as to be kept has no effect: " << tag.Format();
+    }
+
+    MarkNotOrthancAnonymization();
+  }
+
+  void DicomModification::Remove(const DicomTag& tag)
+  {
+    removals_.insert(tag);
+    clearings_.erase(tag);
+    CancelReplacement(tag);
+    privateTagsToKeep_.erase(tag);
+
+    MarkNotOrthancAnonymization();
+  }
+
+  void DicomModification::Clear(const DicomTag& tag)
+  {
+    removals_.erase(tag);
+    clearings_.insert(tag);
+    CancelReplacement(tag);
+    privateTagsToKeep_.erase(tag);
+
+    MarkNotOrthancAnonymization();
+  }
+
+  bool DicomModification::IsRemoved(const DicomTag& tag) const
+  {
+    return removals_.find(tag) != removals_.end();
+  }
+
+  bool DicomModification::IsCleared(const DicomTag& tag) const
+  {
+    return clearings_.find(tag) != clearings_.end();
+  }
+
+  void DicomModification::Replace(const DicomTag& tag,
+                                  const Json::Value& value,
+                                  bool safeForAnonymization)
+  {
+    clearings_.erase(tag);
+    removals_.erase(tag);
+    privateTagsToKeep_.erase(tag);
+    ReplaceInternal(tag, value);
+
+    if (!safeForAnonymization)
+    {
+      MarkNotOrthancAnonymization();
+    }
+  }
+
+
+  bool DicomModification::IsReplaced(const DicomTag& tag) const
+  {
+    return replacements_.find(tag) != replacements_.end();
+  }
+
+  const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const
+  {
+    Replacements::const_iterator it = replacements_.find(tag);
+
+    if (it == replacements_.end())
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+    else
+    {
+      return *it->second;
+    } 
+  }
+
+
+  std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const
+  {
+    const Json::Value& json = GetReplacement(tag);
+
+    if (json.type() != Json::stringValue)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+    else
+    {
+      return json.asString();
+    }    
+  }
+
+
+  void DicomModification::SetRemovePrivateTags(bool removed)
+  {
+    removePrivateTags_ = removed;
+
+    if (!removed)
+    {
+      MarkNotOrthancAnonymization();
+    }
+  }
+
+  void DicomModification::SetLevel(ResourceType level)
+  {
+    uidMap_.clear();
+    level_ = level;
+
+    if (level != ResourceType_Patient)
+    {
+      MarkNotOrthancAnonymization();
+    }
+  }
+
+
+  void DicomModification::SetupAnonymization2008()
+  {
+    // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles
+    // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2008/08_15pu.pdf
+    
+    removals_.insert(DicomTag(0x0008, 0x0014));  // Instance Creator UID
+    //removals_.insert(DicomTag(0x0008, 0x0018));  // SOP Instance UID => set in Apply()
+    removals_.insert(DicomTag(0x0008, 0x0050));  // Accession Number
+    removals_.insert(DicomTag(0x0008, 0x0080));  // Institution Name
+    removals_.insert(DicomTag(0x0008, 0x0081));  // Institution Address
+    removals_.insert(DicomTag(0x0008, 0x0090));  // Referring Physician's Name 
+    removals_.insert(DicomTag(0x0008, 0x0092));  // Referring Physician's Address 
+    removals_.insert(DicomTag(0x0008, 0x0094));  // Referring Physician's Telephone Numbers 
+    removals_.insert(DicomTag(0x0008, 0x1010));  // Station Name 
+    removals_.insert(DicomTag(0x0008, 0x1030));  // Study Description 
+    removals_.insert(DicomTag(0x0008, 0x103e));  // Series Description 
+    removals_.insert(DicomTag(0x0008, 0x1040));  // Institutional Department Name 
+    removals_.insert(DicomTag(0x0008, 0x1048));  // Physician(s) of Record 
+    removals_.insert(DicomTag(0x0008, 0x1050));  // Performing Physicians' Name 
+    removals_.insert(DicomTag(0x0008, 0x1060));  // Name of Physician(s) Reading Study 
+    removals_.insert(DicomTag(0x0008, 0x1070));  // Operators' Name 
+    removals_.insert(DicomTag(0x0008, 0x1080));  // Admitting Diagnoses Description 
+    removals_.insert(DicomTag(0x0008, 0x1155));  // Referenced SOP Instance UID 
+    removals_.insert(DicomTag(0x0008, 0x2111));  // Derivation Description 
+    //removals_.insert(DicomTag(0x0010, 0x0010));  // Patient's Name => cf. below (*)
+    //removals_.insert(DicomTag(0x0010, 0x0020));  // Patient ID => cf. below (*)
+    removals_.insert(DicomTag(0x0010, 0x0030));  // Patient's Birth Date 
+    removals_.insert(DicomTag(0x0010, 0x0032));  // Patient's Birth Time 
+    removals_.insert(DicomTag(0x0010, 0x0040));  // Patient's Sex 
+    removals_.insert(DicomTag(0x0010, 0x1000));  // Other Patient Ids 
+    removals_.insert(DicomTag(0x0010, 0x1001));  // Other Patient Names 
+    removals_.insert(DicomTag(0x0010, 0x1010));  // Patient's Age 
+    removals_.insert(DicomTag(0x0010, 0x1020));  // Patient's Size 
+    removals_.insert(DicomTag(0x0010, 0x1030));  // Patient's Weight 
+    removals_.insert(DicomTag(0x0010, 0x1090));  // Medical Record Locator 
+    removals_.insert(DicomTag(0x0010, 0x2160));  // Ethnic Group 
+    removals_.insert(DicomTag(0x0010, 0x2180));  // Occupation 
+    removals_.insert(DicomTag(0x0010, 0x21b0));  // Additional Patient's History 
+    removals_.insert(DicomTag(0x0010, 0x4000));  // Patient Comments 
+    removals_.insert(DicomTag(0x0018, 0x1000));  // Device Serial Number 
+    removals_.insert(DicomTag(0x0018, 0x1030));  // Protocol Name 
+    //removals_.insert(DicomTag(0x0020, 0x000d));  // Study Instance UID => set in Apply()
+    //removals_.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => set in Apply()
+    removals_.insert(DicomTag(0x0020, 0x0010));  // Study ID 
+    removals_.insert(DicomTag(0x0020, 0x0052));  // Frame of Reference UID 
+    removals_.insert(DicomTag(0x0020, 0x0200));  // Synchronization Frame of Reference UID 
+    removals_.insert(DicomTag(0x0020, 0x4000));  // Image Comments 
+    removals_.insert(DicomTag(0x0040, 0x0275));  // Request Attributes Sequence 
+    removals_.insert(DicomTag(0x0040, 0xa124));  // UID
+    removals_.insert(DicomTag(0x0040, 0xa730));  // Content Sequence 
+    removals_.insert(DicomTag(0x0088, 0x0140));  // Storage Media File-set UID 
+    removals_.insert(DicomTag(0x3006, 0x0024));  // Referenced Frame of Reference UID 
+    removals_.insert(DicomTag(0x3006, 0x00c2));  // Related Frame of Reference UID 
+
+    // Some more removals (from the experience of DICOM files at the CHU of Liege)
+    removals_.insert(DicomTag(0x0010, 0x1040));  // Patient's Address
+    removals_.insert(DicomTag(0x0032, 0x1032));  // Requesting Physician
+    removals_.insert(DicomTag(0x0010, 0x2154));  // PatientTelephoneNumbers
+    removals_.insert(DicomTag(0x0010, 0x2000));  // Medical Alerts
+
+    // Set the DeidentificationMethod tag
+    ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2008);
+  }
+  
+
+#if 0
+  /**
+   * This is a manual implementation by Alain Mazy. Only kept for reference.
+   * https://bitbucket.org/sjodogne/orthanc/commits/c6defdc4c611fca2ab528ba2c6937a742e0329a8?at=issue-46-anonymization
+   **/
+  
+  void DicomModification::SetupAnonymization2011()
+  {
+    // This is Table E.1-1 from PS 3.15-2011 - DICOM Part 15: Security and System Management Profiles
+    // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2011/11_15pu.pdf
+    
+    removals_.insert(DicomTag(0x0000, 0x1000));  // Affected SOP Instance UID
+    removals_.insert(DicomTag(0x0000, 0x1001));  // Requested SOP Instance UID
+    removals_.insert(DicomTag(0x0002, 0x0003));  // Media Storage SOP Instance UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances
+    removals_.insert(DicomTag(0x0004, 0x1511));  // Referenced SOP Instance UID in File
+    removals_.insert(DicomTag(0x0008, 0x0010));  // Irradiation Event UID
+    removals_.insert(DicomTag(0x0008, 0x0014));  // Instance Creator UID
+    //removals_.insert(DicomTag(0x0008, 0x0018));  // SOP Instance UID => set in Apply()
+    clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date
+    clearings_.insert(DicomTag(0x0008, 0x0021)); // Series Date
+    clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time
+    clearings_.insert(DicomTag(0x0008, 0x0031)); // Series Time
+    removals_.insert(DicomTag(0x0008, 0x0022));  // Acquisition Date
+    removals_.insert(DicomTag(0x0008, 0x0023));  // Content Date
+    removals_.insert(DicomTag(0x0008, 0x0024));  // Overlay Date
+    removals_.insert(DicomTag(0x0008, 0x0025));  // Curve Date
+    removals_.insert(DicomTag(0x0008, 0x002a));  // Acquisition DateTime
+    removals_.insert(DicomTag(0x0008, 0x0032));  // Acquisition Time
+    removals_.insert(DicomTag(0x0008, 0x0033));  // Content Time
+    removals_.insert(DicomTag(0x0008, 0x0034));  // Overlay Time
+    removals_.insert(DicomTag(0x0008, 0x0035));  // Curve Time
+    removals_.insert(DicomTag(0x0008, 0x0050));  // Accession Number
+    removals_.insert(DicomTag(0x0008, 0x0058));  // Failed SOP Instance UID List
+    removals_.insert(DicomTag(0x0008, 0x0080));  // Institution Name
+    removals_.insert(DicomTag(0x0008, 0x0081));  // Institution Address
+    removals_.insert(DicomTag(0x0008, 0x0082));  // Institution Code Sequence
+    removals_.insert(DicomTag(0x0008, 0x0090));  // Referring Physician's Name
+    removals_.insert(DicomTag(0x0008, 0x0092));  // Referring Physician's Address 
+    removals_.insert(DicomTag(0x0008, 0x0094));  // Referring Physician's Telephone Numbers 
+    removals_.insert(DicomTag(0x0008, 0x0096));  // Referring Physician's Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x010d));  // Context Group Extension Creator UID
+    removals_.insert(DicomTag(0x0008, 0x0201));  // Timezone Offset From UTC
+    removals_.insert(DicomTag(0x0008, 0x0300));  // Current Patient Location
+    removals_.insert(DicomTag(0x0008, 0x1010));  // Station Name
+    removals_.insert(DicomTag(0x0008, 0x1030));  // Study Description 
+    removals_.insert(DicomTag(0x0008, 0x103e));  // Series Description 
+    removals_.insert(DicomTag(0x0008, 0x1040));  // Institutional Department Name 
+    removals_.insert(DicomTag(0x0008, 0x1048));  // Physician(s) of Record 
+    removals_.insert(DicomTag(0x0008, 0x1049));  // Physician(s) of Record Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x1050));  // Performing Physicians' Name
+    removals_.insert(DicomTag(0x0008, 0x1052));  // Performing Physicians Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x1060));  // Name of Physician(s) Reading Study
+    removals_.insert(DicomTag(0x0008, 0x1062));  // Physician Reading Study Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x1070));  // Operators' Name
+    removals_.insert(DicomTag(0x0008, 0x1072));  // Operators' Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x1080));  // Admitting Diagnoses Description
+    removals_.insert(DicomTag(0x0008, 0x1084));  // Admitting Diagnoses Code Sequence
+    removals_.insert(DicomTag(0x0008, 0x1110));  // Referenced Study Sequence
+    removals_.insert(DicomTag(0x0008, 0x1111));  // Referenced Performed Procedure Step Sequence
+    removals_.insert(DicomTag(0x0008, 0x1120));  // Referenced Patient Sequence
+    removals_.insert(DicomTag(0x0008, 0x1140));  // Referenced Image Sequence
+    removals_.insert(DicomTag(0x0008, 0x1155));  // Referenced SOP Instance UID
+    removals_.insert(DicomTag(0x0008, 0x1195));  // Transaction UID
+    removals_.insert(DicomTag(0x0008, 0x2111));  // Derivation Description
+    removals_.insert(DicomTag(0x0008, 0x2112));  // Source Image Sequence
+    removals_.insert(DicomTag(0x0008, 0x4000));  // Identifying Comments
+    removals_.insert(DicomTag(0x0008, 0x9123));  // Creator Version UID
+    //removals_.insert(DicomTag(0x0010, 0x0010));  // Patient's Name => cf. below (*)
+    //removals_.insert(DicomTag(0x0010, 0x0020));  // Patient ID => cf. below (*)
+    removals_.insert(DicomTag(0x0010, 0x0030));  // Patient's Birth Date 
+    removals_.insert(DicomTag(0x0010, 0x0032));  // Patient's Birth Time 
+    clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex
+    removals_.insert(DicomTag(0x0010, 0x0050));  // Patient's Insurance Plan Code Sequence
+    removals_.insert(DicomTag(0x0010, 0x0101));  // Patient's Primary Language Code Sequence
+    removals_.insert(DicomTag(0x0010, 0x0102));  // Patient's Primary Language Modifier Code Sequence
+    removals_.insert(DicomTag(0x0010, 0x1000));  // Other Patient Ids
+    removals_.insert(DicomTag(0x0010, 0x1001));  // Other Patient Names 
+    removals_.insert(DicomTag(0x0010, 0x1002));  // Other Patient IDs Sequence
+    removals_.insert(DicomTag(0x0010, 0x1005));  // Patient's Birth Name
+    removals_.insert(DicomTag(0x0010, 0x1010));  // Patient's Age
+    removals_.insert(DicomTag(0x0010, 0x1020));  // Patient's Size 
+    removals_.insert(DicomTag(0x0010, 0x1030));  // Patient's Weight 
+    removals_.insert(DicomTag(0x0010, 0x1040));  // Patient's Address
+    removals_.insert(DicomTag(0x0010, 0x1050));  // Insurance Plan Identification
+    removals_.insert(DicomTag(0x0010, 0x1060));  // Patient's Mother's Birth Name
+    removals_.insert(DicomTag(0x0010, 0x1080));  // Military Rank
+    removals_.insert(DicomTag(0x0010, 0x1081));  // Branch of Service
+    removals_.insert(DicomTag(0x0010, 0x1090));  // Medical Record Locator
+    removals_.insert(DicomTag(0x0010, 0x2000));  // Medical Alerts
+    removals_.insert(DicomTag(0x0010, 0x2110));  // Allergies
+    removals_.insert(DicomTag(0x0010, 0x2150));  // Country of Residence
+    removals_.insert(DicomTag(0x0010, 0x2152));  // Region of Residence
+    removals_.insert(DicomTag(0x0010, 0x2154));  // PatientTelephoneNumbers
+    removals_.insert(DicomTag(0x0010, 0x2160));  // Ethnic Group
+    removals_.insert(DicomTag(0x0010, 0x2180));  // Occupation 
+    removals_.insert(DicomTag(0x0010, 0x21a0));  // Smoking Status
+    removals_.insert(DicomTag(0x0010, 0x21b0));  // Additional Patient's History
+    removals_.insert(DicomTag(0x0010, 0x21c0));  // Pregnancy Status
+    removals_.insert(DicomTag(0x0010, 0x21d0));  // Last Menstrual Date
+    removals_.insert(DicomTag(0x0010, 0x21f0));  // Patient's Religious Preference
+    removals_.insert(DicomTag(0x0010, 0x2203));  // Patient's Sex Neutered
+    removals_.insert(DicomTag(0x0010, 0x2297));  // Responsible Person
+    removals_.insert(DicomTag(0x0010, 0x2299));  // Responsible Organization
+    removals_.insert(DicomTag(0x0010, 0x4000));  // Patient Comments
+    removals_.insert(DicomTag(0x0018, 0x0010));  // Contrast Bolus Agent
+    removals_.insert(DicomTag(0x0018, 0x1000));  // Device Serial Number
+    removals_.insert(DicomTag(0x0018, 0x1002));  // Device UID
+    removals_.insert(DicomTag(0x0018, 0x1004));  // Plate ID
+    removals_.insert(DicomTag(0x0018, 0x1005));  // Generator ID
+    removals_.insert(DicomTag(0x0018, 0x1007));  // Cassette ID
+    removals_.insert(DicomTag(0x0018, 0x1008));  // Gantry ID
+    removals_.insert(DicomTag(0x0018, 0x1030));  // Protocol Name
+    removals_.insert(DicomTag(0x0018, 0x1400));  // Acquisition Device Processing Description
+    removals_.insert(DicomTag(0x0018, 0x4000));  // Acquisition Comments
+    removals_.insert(DicomTag(0x0018, 0x700a));  // Detector ID
+    removals_.insert(DicomTag(0x0018, 0xa003));  // Contribution Description
+    removals_.insert(DicomTag(0x0018, 0x9424));  // Acquisition Protocol Description
+    //removals_.insert(DicomTag(0x0020, 0x000d));  // Study Instance UID => set in Apply()
+    //removals_.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => set in Apply()
+    removals_.insert(DicomTag(0x0020, 0x0010));  // Study ID
+    removals_.insert(DicomTag(0x0020, 0x0052));  // Frame of Reference UID 
+    removals_.insert(DicomTag(0x0020, 0x0200));  // Synchronization Frame of Reference UID 
+    removals_.insert(DicomTag(0x0020, 0x3401));  // Modifying Device ID
+    removals_.insert(DicomTag(0x0020, 0x3404));  // Modifying Device Manufacturer
+    removals_.insert(DicomTag(0x0020, 0x3406));  // Modified Image Description
+    removals_.insert(DicomTag(0x0020, 0x4000));  // Image Comments
+    removals_.insert(DicomTag(0x0020, 0x9158));  // Frame Comments
+    removals_.insert(DicomTag(0x0020, 0x9161));  // Concatenation UID
+    removals_.insert(DicomTag(0x0020, 0x9164));  // Dimension Organization UID
+    //removals_.insert(DicomTag(0x0028, 0x1199));  // Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances
+    //removals_.insert(DicomTag(0x0028, 0x1214));  // Large Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances
+    removals_.insert(DicomTag(0x0028, 0x4000));  // Image Presentation Comments
+    removals_.insert(DicomTag(0x0032, 0x0012));  // Study ID Issuer
+    removals_.insert(DicomTag(0x0032, 0x1020));  // Scheduled Study Location
+    removals_.insert(DicomTag(0x0032, 0x1021));  // Scheduled Study Location AE Title
+    removals_.insert(DicomTag(0x0032, 0x1030));  // Reason for Study
+    removals_.insert(DicomTag(0x0032, 0x1032));  // Requesting Physician
+    removals_.insert(DicomTag(0x0032, 0x1033));  // Requesting Service
+    removals_.insert(DicomTag(0x0032, 0x1060));  // Requesting Procedure Description
+    removals_.insert(DicomTag(0x0032, 0x1070));  // Requested Contrast Agent
+    removals_.insert(DicomTag(0x0032, 0x4000));  // Study Comments
+    removals_.insert(DicomTag(0x0038, 0x0010));  // Admission ID
+    removals_.insert(DicomTag(0x0038, 0x0011));  // Issuer of Admission ID
+    removals_.insert(DicomTag(0x0038, 0x001e));  // Scheduled Patient Institution Residence
+    removals_.insert(DicomTag(0x0038, 0x0020));  // Admitting Date
+    removals_.insert(DicomTag(0x0038, 0x0021));  // Admitting Time
+    removals_.insert(DicomTag(0x0038, 0x0040));  // Discharge Diagnosis Description
+    removals_.insert(DicomTag(0x0038, 0x0050));  // Special Needs
+    removals_.insert(DicomTag(0x0038, 0x0060));  // Service Episode ID
+    removals_.insert(DicomTag(0x0038, 0x0061));  // Issuer of Service Episode ID
+    removals_.insert(DicomTag(0x0038, 0x0062));  // Service Episode Description
+    removals_.insert(DicomTag(0x0038, 0x0400));  // Patient's Institution Residence
+    removals_.insert(DicomTag(0x0038, 0x0500));  // Patient State
+    removals_.insert(DicomTag(0x0038, 0x4000));  // Visit Comments
+    removals_.insert(DicomTag(0x0038, 0x1234));  // Referenced Patient Alias Sequence
+    removals_.insert(DicomTag(0x0040, 0x0001));  // Scheduled Station AE Title
+    removals_.insert(DicomTag(0x0040, 0x0002));  // Scheduled Procedure Step Start Date
+    removals_.insert(DicomTag(0x0040, 0x0003));  // Scheduled Procedure Step Start Time
+    removals_.insert(DicomTag(0x0040, 0x0004));  // Scheduled Procedure Step End Date
+    removals_.insert(DicomTag(0x0040, 0x0005));  // Scheduled Procedure Step End Time
+    removals_.insert(DicomTag(0x0040, 0x0006));  // Scheduled Performing Physician Name
+    removals_.insert(DicomTag(0x0040, 0x0007));  // Scheduled Procedure Step Description
+    removals_.insert(DicomTag(0x0040, 0x000b));  // Scheduled Performing Physician Identification Sequence
+    removals_.insert(DicomTag(0x0040, 0x0010));  // Scheduled Station Name
+    removals_.insert(DicomTag(0x0040, 0x0011));  // Scheduled Procedure Step Location
+    removals_.insert(DicomTag(0x0040, 0x0012));  // Pre-Medication
+    removals_.insert(DicomTag(0x0040, 0x0241));  // Performed Station AE Title
+    removals_.insert(DicomTag(0x0040, 0x0242));  // Performed Station Name
+    removals_.insert(DicomTag(0x0040, 0x0243));  // Performed Location
+    removals_.insert(DicomTag(0x0040, 0x0244));  // Performed Procedure Step Start Date
+    removals_.insert(DicomTag(0x0040, 0x0245));  // Performed Procedure Step Start Time
+    removals_.insert(DicomTag(0x0040, 0x0248));  // Performed Station Name Code Sequence
+    removals_.insert(DicomTag(0x0040, 0x0253));  // Performed Procedure Step ID
+    removals_.insert(DicomTag(0x0040, 0x0254));  // Performed Procedure Step Description
+    removals_.insert(DicomTag(0x0040, 0x0275));  // Request Attributes Sequence
+    removals_.insert(DicomTag(0x0040, 0x0280));  // Comments on Performed Procedure Step
+    removals_.insert(DicomTag(0x0040, 0x0555));  // Acquisition Context Sequence
+    removals_.insert(DicomTag(0x0040, 0x1001));  // Requested Procedure ID
+    removals_.insert(DicomTag(0x0040, 0x1010));  // Names of Intended Recipient of Results
+    removals_.insert(DicomTag(0x0040, 0x1011));  // Intended Recipient of Results Identification Sequence
+    removals_.insert(DicomTag(0x0040, 0x1004));  // Patient Transport Arrangements
+    removals_.insert(DicomTag(0x0040, 0x1005));  // Requested Procedure Location
+    removals_.insert(DicomTag(0x0040, 0x1101));  // Person Identification Code Sequence
+    removals_.insert(DicomTag(0x0040, 0x1102));  // Person Address
+    removals_.insert(DicomTag(0x0040, 0x1103));  // Person Telephone Numbers
+    removals_.insert(DicomTag(0x0040, 0x1400));  // Requested Procedure Comments
+    removals_.insert(DicomTag(0x0040, 0x2001));  // Reason for Imaging Service Request
+    removals_.insert(DicomTag(0x0040, 0x2008));  // Order Entered By
+    removals_.insert(DicomTag(0x0040, 0x2009));  // Order Enterer Location
+    removals_.insert(DicomTag(0x0040, 0x2010));  // Order Callback Phone Number
+    removals_.insert(DicomTag(0x0040, 0x2016));  // Placer Order Number of Imaging Service Request
+    removals_.insert(DicomTag(0x0040, 0x2017));  // Filler Order Number of Imaging Service Request
+    removals_.insert(DicomTag(0x0040, 0x2400));  // Imaging Service Request Comments
+    removals_.insert(DicomTag(0x0040, 0x4023));  // Referenced General Purpose Scheduled Procedure Step Transaction UID
+    removals_.insert(DicomTag(0x0040, 0x4025));  // Scheduled Station Name Code Sequence
+    removals_.insert(DicomTag(0x0040, 0x4027));  // Scheduled Station Geographic Location Code Sequence
+    removals_.insert(DicomTag(0x0040, 0x4030));  // Performed Station Geographic Location Code Sequence
+    removals_.insert(DicomTag(0x0040, 0x4034));  // Scheduled Human Performers Sequence
+    removals_.insert(DicomTag(0x0040, 0x4035));  // Actual Human Performers Sequence
+    removals_.insert(DicomTag(0x0040, 0x4036));  // Human Performers Organization
+    removals_.insert(DicomTag(0x0040, 0x4037));  // Human Performers Name
+    removals_.insert(DicomTag(0x0040, 0xa027));  // Verifying Organization
+    removals_.insert(DicomTag(0x0040, 0xa073));  // Verifying Observer Sequence
+    removals_.insert(DicomTag(0x0040, 0xa075));  // Verifying Observer Name
+    removals_.insert(DicomTag(0x0040, 0xa078));  // Author Observer Sequence
+    removals_.insert(DicomTag(0x0040, 0xa07a));  // Participant Sequence
+    removals_.insert(DicomTag(0x0040, 0xa07c));  // Custodial Organization Sequence
+    removals_.insert(DicomTag(0x0040, 0xa088));  // Verifying Observer Identification Code Sequence
+    removals_.insert(DicomTag(0x0040, 0xa123));  // Person Name
+    removals_.insert(DicomTag(0x0040, 0xa124));  // UID
+    removals_.insert(DicomTag(0x0040, 0xa730));  // Content Sequence 
+    removals_.insert(DicomTag(0x0040, 0x3001));  // Confidentiality Constraint on Patient Data Description
+    removals_.insert(DicomTag(0x0040, 0xdb0c));  // Template Extension Organization UID
+    removals_.insert(DicomTag(0x0040, 0xdb0d));  // Template Extension Creator UID
+    removals_.insert(DicomTag(0x0070, 0x0001));  // Graphic Annotation Sequence
+    removals_.insert(DicomTag(0x0070, 0x0084));  // Content Creator's Name
+    removals_.insert(DicomTag(0x0070, 0x0086));  // Content Creator's Identification Code Sequence
+    removals_.insert(DicomTag(0x0070, 0x031a));  // Fiducial UID
+    removals_.insert(DicomTag(0x0088, 0x0140));  // Storage Media File-set UID
+    removals_.insert(DicomTag(0x0088, 0x0200));  // Icon Image Sequence
+    removals_.insert(DicomTag(0x0088, 0x0904));  // Topic Title
+    removals_.insert(DicomTag(0x0088, 0x0906));  // Topic Subject
+    removals_.insert(DicomTag(0x0088, 0x0910));  // Topic Author
+    removals_.insert(DicomTag(0x0088, 0x0912));  // Topic Key Words
+    removals_.insert(DicomTag(0x0400, 0x0100));  // Digital Signature UID
+    removals_.insert(DicomTag(0x0400, 0x0402));  // Referenced Digital Signature Sequence
+    removals_.insert(DicomTag(0x0400, 0x0403));  // Referenced SOP Instance MAC Sequence
+    removals_.insert(DicomTag(0x0400, 0x0404));  // MAC
+    removals_.insert(DicomTag(0x0400, 0x0550));  // Modified Attributes Sequence
+    removals_.insert(DicomTag(0x0400, 0x0561));  // Original Attributes Sequence
+    removals_.insert(DicomTag(0x2030, 0x0020));  // Text String
+    removals_.insert(DicomTag(0x3006, 0x0024));  // Referenced Frame of Reference UID
+    removals_.insert(DicomTag(0x3006, 0x00c2));  // Related Frame of Reference UID 
+    removals_.insert(DicomTag(0x300a, 0x0013));  // Dose Reference UID
+    removals_.insert(DicomTag(0x300e, 0x0008));  // Reviewer Name
+    removals_.insert(DicomTag(0x4000, 0x0010));  // Arbitrary
+    removals_.insert(DicomTag(0x4000, 0x4000));  // Text Comments
+    removals_.insert(DicomTag(0x4008, 0x0042));  // Results ID Issuer
+    removals_.insert(DicomTag(0x4008, 0x0102));  // Interpretation Recorder
+    removals_.insert(DicomTag(0x4008, 0x010a));  // Interpretation Transcriber
+    removals_.insert(DicomTag(0x4008, 0x010b));  // Interpretation Text
+    removals_.insert(DicomTag(0x4008, 0x010c));  // Interpretation Author
+    removals_.insert(DicomTag(0x4008, 0x0111));  // Interpretation Approver Sequence
+    removals_.insert(DicomTag(0x4008, 0x0114));  // Physician Approving Interpretation
+    removals_.insert(DicomTag(0x4008, 0x0115));  // Interpretation Diagnosis Description
+    removals_.insert(DicomTag(0x4008, 0x0118));  // Results Distribution List Sequence
+    removals_.insert(DicomTag(0x4008, 0x0119));  // Distribution Name
+    removals_.insert(DicomTag(0x4008, 0x011a));  // Distribution Address
+    removals_.insert(DicomTag(0x4008, 0x0202));  // Interpretation ID Issuer
+    removals_.insert(DicomTag(0x4008, 0x0300));  // Impressions
+    removals_.insert(DicomTag(0x4008, 0x4000));  // Results Comments
+    removals_.insert(DicomTag(0xfffa, 0xfffa));  // Digital Signature Sequence
+    removals_.insert(DicomTag(0xfffc, 0xfffc));  // Data Set Trailing Padding
+    //removals_.insert(DicomTag(0x60xx, 0x4000));  // Overlay Comments => TODO
+    //removals_.insert(DicomTag(0x60xx, 0x3000));  // Overlay Data => TODO
+
+    // Set the DeidentificationMethod tag
+    ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2011);
+  }
+#endif
+  
+  
+
+  void DicomModification::SetupAnonymization2017c()
+  {
+    /**
+     * This is Table E.1-1 from PS 3.15-2017c (DICOM Part 15: Security
+     * and System Management Profiles), "basic profile" column. It was
+     * generated automatically with the
+     * "../Resources/GenerateAnonymizationProfile.py" script.
+     * https://raw.githubusercontent.com/jodogne/dicom-specification/master/2017c/part15.pdf
+     **/
+    
+    // TODO: (50xx,xxxx) with rule X                                 // Curve Data
+    // TODO: (60xx,3000) with rule X                                 // Overlay Data
+    // TODO: (60xx,4000) with rule X                                 // Overlay Comments
+    // Tag (0x0008, 0x0018) is set in Apply()                        // SOP Instance UID
+    // Tag (0x0010, 0x0010) is set below (*)                         // Patient's Name
+    // Tag (0x0010, 0x0020) is set below (*)                         // Patient ID
+    // Tag (0x0020, 0x000d) is set in Apply()                        // Study Instance UID
+    // Tag (0x0020, 0x000e) is set in Apply()                        // Series Instance UID
+    clearings_.insert(DicomTag(0x0008, 0x0020));                     // Study Date
+    clearings_.insert(DicomTag(0x0008, 0x0023));  /* Z/D */          // Content Date
+    clearings_.insert(DicomTag(0x0008, 0x0030));                     // Study Time
+    clearings_.insert(DicomTag(0x0008, 0x0033));  /* Z/D */          // Content Time
+    clearings_.insert(DicomTag(0x0008, 0x0050));                     // Accession Number
+    clearings_.insert(DicomTag(0x0008, 0x0090));                     // Referring Physician's Name
+    clearings_.insert(DicomTag(0x0008, 0x009c));                     // Consulting Physician's Name
+    clearings_.insert(DicomTag(0x0010, 0x0030));                     // Patient's Birth Date
+    clearings_.insert(DicomTag(0x0010, 0x0040));                     // Patient's Sex
+    clearings_.insert(DicomTag(0x0018, 0x0010));  /* Z/D */          // Contrast Bolus Agent
+    clearings_.insert(DicomTag(0x0020, 0x0010));                     // Study ID
+    clearings_.insert(DicomTag(0x0040, 0x1101));  /* D */            // Person Identification Code Sequence
+    clearings_.insert(DicomTag(0x0040, 0x2016));                     // Placer Order Number / Imaging Service Request
+    clearings_.insert(DicomTag(0x0040, 0x2017));                     // Filler Order Number / Imaging Service Request
+    clearings_.insert(DicomTag(0x0040, 0xa073));  /* D */            // Verifying Observer Sequence
+    clearings_.insert(DicomTag(0x0040, 0xa075));  /* D */            // Verifying Observer Name
+    clearings_.insert(DicomTag(0x0040, 0xa088));                     // Verifying Observer Identification Code Sequence
+    clearings_.insert(DicomTag(0x0040, 0xa123));  /* D */            // Person Name
+    clearings_.insert(DicomTag(0x0070, 0x0001));  /* D */            // Graphic Annotation Sequence
+    clearings_.insert(DicomTag(0x0070, 0x0084));                     // Content Creator's Name
+    removals_.insert(DicomTag(0x0000, 0x1000));                      // Affected SOP Instance UID
+    removals_.insert(DicomTag(0x0000, 0x1001));   /* TODO UID */     // Requested SOP Instance UID
+    removals_.insert(DicomTag(0x0002, 0x0003));   /* TODO UID */     // Media Storage SOP Instance UID
+    removals_.insert(DicomTag(0x0004, 0x1511));   /* TODO UID */     // Referenced SOP Instance UID in File
+    removals_.insert(DicomTag(0x0008, 0x0014));   /* TODO UID */     // Instance Creator UID
+    removals_.insert(DicomTag(0x0008, 0x0015));                      // Instance Coercion DateTime
+    removals_.insert(DicomTag(0x0008, 0x0021));   /* X/D */          // Series Date
+    removals_.insert(DicomTag(0x0008, 0x0022));   /* X/Z */          // Acquisition Date
+    removals_.insert(DicomTag(0x0008, 0x0024));                      // Overlay Date
+    removals_.insert(DicomTag(0x0008, 0x0025));                      // Curve Date
+    removals_.insert(DicomTag(0x0008, 0x002a));   /* X/D */          // Acquisition DateTime
+    removals_.insert(DicomTag(0x0008, 0x0031));   /* X/D */          // Series Time
+    removals_.insert(DicomTag(0x0008, 0x0032));   /* X/Z */          // Acquisition Time
+    removals_.insert(DicomTag(0x0008, 0x0034));                      // Overlay Time
+    removals_.insert(DicomTag(0x0008, 0x0035));                      // Curve Time
+    removals_.insert(DicomTag(0x0008, 0x0058));   /* TODO UID */     // Failed SOP Instance UID List
+    removals_.insert(DicomTag(0x0008, 0x0080));   /* X/Z/D */        // Institution Name
+    removals_.insert(DicomTag(0x0008, 0x0081));                      // Institution Address
+    removals_.insert(DicomTag(0x0008, 0x0082));   /* X/Z/D */        // Institution Code Sequence
+    removals_.insert(DicomTag(0x0008, 0x0092));                      // Referring Physician's Address
+    removals_.insert(DicomTag(0x0008, 0x0094));                      // Referring Physician's Telephone Numbers
+    removals_.insert(DicomTag(0x0008, 0x0096));                      // Referring Physician Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x009d));                      // Consulting Physician Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x0201));                      // Timezone Offset From UTC
+    removals_.insert(DicomTag(0x0008, 0x1010));   /* X/Z/D */        // Station Name
+    removals_.insert(DicomTag(0x0008, 0x1030));                      // Study Description
+    removals_.insert(DicomTag(0x0008, 0x103e));                      // Series Description
+    removals_.insert(DicomTag(0x0008, 0x1040));                      // Institutional Department Name
+    removals_.insert(DicomTag(0x0008, 0x1048));                      // Physician(s) of Record
+    removals_.insert(DicomTag(0x0008, 0x1049));                      // Physician(s) of Record Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x1050));                      // Performing Physicians' Name
+    removals_.insert(DicomTag(0x0008, 0x1052));                      // Performing Physician Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x1060));                      // Name of Physician(s) Reading Study
+    removals_.insert(DicomTag(0x0008, 0x1062));                      // Physician(s) Reading Study Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x1070));   /* X/Z/D */        // Operators' Name
+    removals_.insert(DicomTag(0x0008, 0x1072));   /* X/D */          // Operators' Identification Sequence
+    removals_.insert(DicomTag(0x0008, 0x1080));                      // Admitting Diagnoses Description
+    removals_.insert(DicomTag(0x0008, 0x1084));                      // Admitting Diagnoses Code Sequence
+    removals_.insert(DicomTag(0x0008, 0x1110));   /* X/Z */          // Referenced Study Sequence
+    removals_.insert(DicomTag(0x0008, 0x1111));   /* X/Z/D */        // Referenced Performed Procedure Step Sequence
+    removals_.insert(DicomTag(0x0008, 0x1120));                      // Referenced Patient Sequence
+    removals_.insert(DicomTag(0x0008, 0x1140));   /* X/Z/U* */       // Referenced Image Sequence
+    removals_.insert(DicomTag(0x0008, 0x1155));   /* TODO UID */     // Referenced SOP Instance UID
+    removals_.insert(DicomTag(0x0008, 0x1195));   /* TODO UID */     // Transaction UID
+    removals_.insert(DicomTag(0x0008, 0x2111));                      // Derivation Description
+    removals_.insert(DicomTag(0x0008, 0x2112));   /* X/Z/U* */       // Source Image Sequence
+    removals_.insert(DicomTag(0x0008, 0x3010));   /* TODO UID */     // Irradiation Event UID
+    removals_.insert(DicomTag(0x0008, 0x4000));                      // Identifying Comments
+    removals_.insert(DicomTag(0x0010, 0x0021));                      // Issuer of Patient ID
+    removals_.insert(DicomTag(0x0010, 0x0032));                      // Patient's Birth Time
+    removals_.insert(DicomTag(0x0010, 0x0050));                      // Patient's Insurance Plan Code Sequence
+    removals_.insert(DicomTag(0x0010, 0x0101));                      // Patient's Primary Language Code Sequence
+    removals_.insert(DicomTag(0x0010, 0x0102));                      // Patient's Primary Language Modifier Code Sequence
+    removals_.insert(DicomTag(0x0010, 0x1000));                      // Other Patient IDs
+    removals_.insert(DicomTag(0x0010, 0x1001));                      // Other Patient Names
+    removals_.insert(DicomTag(0x0010, 0x1002));                      // Other Patient IDs Sequence
+    removals_.insert(DicomTag(0x0010, 0x1005));                      // Patient's Birth Name
+    removals_.insert(DicomTag(0x0010, 0x1010));                      // Patient's Age
+    removals_.insert(DicomTag(0x0010, 0x1020));                      // Patient's Size
+    removals_.insert(DicomTag(0x0010, 0x1030));                      // Patient's Weight
+    removals_.insert(DicomTag(0x0010, 0x1040));                      // Patient Address
+    removals_.insert(DicomTag(0x0010, 0x1050));                      // Insurance Plan Identification
+    removals_.insert(DicomTag(0x0010, 0x1060));                      // Patient's Mother's Birth Name
+    removals_.insert(DicomTag(0x0010, 0x1080));                      // Military Rank
+    removals_.insert(DicomTag(0x0010, 0x1081));                      // Branch of Service
+    removals_.insert(DicomTag(0x0010, 0x1090));                      // Medical Record Locator
+    removals_.insert(DicomTag(0x0010, 0x1100));                      // Referenced Patient Photo Sequence
+    removals_.insert(DicomTag(0x0010, 0x2000));                      // Medical Alerts
+    removals_.insert(DicomTag(0x0010, 0x2110));                      // Allergies
+    removals_.insert(DicomTag(0x0010, 0x2150));                      // Country of Residence
+    removals_.insert(DicomTag(0x0010, 0x2152));                      // Region of Residence
+    removals_.insert(DicomTag(0x0010, 0x2154));                      // Patient's Telephone Numbers
+    removals_.insert(DicomTag(0x0010, 0x2155));                      // Patient's Telecom Information
+    removals_.insert(DicomTag(0x0010, 0x2160));                      // Ethnic Group
+    removals_.insert(DicomTag(0x0010, 0x2180));                      // Occupation
+    removals_.insert(DicomTag(0x0010, 0x21a0));                      // Smoking Status
+    removals_.insert(DicomTag(0x0010, 0x21b0));                      // Additional Patient's History
+    removals_.insert(DicomTag(0x0010, 0x21c0));                      // Pregnancy Status
+    removals_.insert(DicomTag(0x0010, 0x21d0));                      // Last Menstrual Date
+    removals_.insert(DicomTag(0x0010, 0x21f0));                      // Patient's Religious Preference
+    removals_.insert(DicomTag(0x0010, 0x2203));   /* X/Z */          // Patient Sex Neutered
+    removals_.insert(DicomTag(0x0010, 0x2297));                      // Responsible Person
+    removals_.insert(DicomTag(0x0010, 0x2299));                      // Responsible Organization
+    removals_.insert(DicomTag(0x0010, 0x4000));                      // Patient Comments
+    removals_.insert(DicomTag(0x0018, 0x1000));   /* X/Z/D */        // Device Serial Number
+    removals_.insert(DicomTag(0x0018, 0x1002));   /* TODO UID */     // Device UID
+    removals_.insert(DicomTag(0x0018, 0x1004));                      // Plate ID
+    removals_.insert(DicomTag(0x0018, 0x1005));                      // Generator ID
+    removals_.insert(DicomTag(0x0018, 0x1007));                      // Cassette ID
+    removals_.insert(DicomTag(0x0018, 0x1008));                      // Gantry ID
+    removals_.insert(DicomTag(0x0018, 0x1030));   /* X/D */          // Protocol Name
+    removals_.insert(DicomTag(0x0018, 0x1400));   /* X/D */          // Acquisition Device Processing Description
+    removals_.insert(DicomTag(0x0018, 0x2042));   /* TODO UID */     // Target UID
+    removals_.insert(DicomTag(0x0018, 0x4000));                      // Acquisition Comments
+    removals_.insert(DicomTag(0x0018, 0x700a));   /* X/D */          // Detector ID
+    removals_.insert(DicomTag(0x0018, 0x9424));                      // Acquisition Protocol Description
+    removals_.insert(DicomTag(0x0018, 0x9516));   /* X/D */          // Start Acquisition DateTime
+    removals_.insert(DicomTag(0x0018, 0x9517));   /* X/D */          // End Acquisition DateTime
+    removals_.insert(DicomTag(0x0018, 0xa003));                      // Contribution Description
+    removals_.insert(DicomTag(0x0020, 0x0052));   /* TODO UID */     // Frame of Reference UID
+    removals_.insert(DicomTag(0x0020, 0x0200));   /* TODO UID */     // Synchronization Frame of Reference UID
+    removals_.insert(DicomTag(0x0020, 0x3401));                      // Modifying Device ID
+    removals_.insert(DicomTag(0x0020, 0x3404));                      // Modifying Device Manufacturer
+    removals_.insert(DicomTag(0x0020, 0x3406));                      // Modified Image Description
+    removals_.insert(DicomTag(0x0020, 0x4000));                      // Image Comments
+    removals_.insert(DicomTag(0x0020, 0x9158));                      // Frame Comments
+    removals_.insert(DicomTag(0x0020, 0x9161));   /* TODO UID */     // Concatenation UID
+    removals_.insert(DicomTag(0x0020, 0x9164));   /* TODO UID */     // Dimension Organization UID
+    removals_.insert(DicomTag(0x0028, 0x1199));   /* TODO UID */     // Palette Color Lookup Table UID
+    removals_.insert(DicomTag(0x0028, 0x1214));   /* TODO UID */     // Large Palette Color Lookup Table UID
+    removals_.insert(DicomTag(0x0028, 0x4000));                      // Image Presentation Comments
+    removals_.insert(DicomTag(0x0032, 0x0012));                      // Study ID Issuer
+    removals_.insert(DicomTag(0x0032, 0x1020));                      // Scheduled Study Location
+    removals_.insert(DicomTag(0x0032, 0x1021));                      // Scheduled Study Location AE Title
+    removals_.insert(DicomTag(0x0032, 0x1030));                      // Reason for Study
+    removals_.insert(DicomTag(0x0032, 0x1032));                      // Requesting Physician
+    removals_.insert(DicomTag(0x0032, 0x1033));                      // Requesting Service
+    removals_.insert(DicomTag(0x0032, 0x1060));   /* X/Z */          // Requested Procedure Description
+    removals_.insert(DicomTag(0x0032, 0x1070));                      // Requested Contrast Agent
+    removals_.insert(DicomTag(0x0032, 0x4000));                      // Study Comments
+    removals_.insert(DicomTag(0x0038, 0x0004));                      // Referenced Patient Alias Sequence
+    removals_.insert(DicomTag(0x0038, 0x0010));                      // Admission ID
+    removals_.insert(DicomTag(0x0038, 0x0011));                      // Issuer of Admission ID
+    removals_.insert(DicomTag(0x0038, 0x001e));                      // Scheduled Patient Institution Residence
+    removals_.insert(DicomTag(0x0038, 0x0020));                      // Admitting Date
+    removals_.insert(DicomTag(0x0038, 0x0021));                      // Admitting Time
+    removals_.insert(DicomTag(0x0038, 0x0040));                      // Discharge Diagnosis Description
+    removals_.insert(DicomTag(0x0038, 0x0050));                      // Special Needs
+    removals_.insert(DicomTag(0x0038, 0x0060));                      // Service Episode ID
+    removals_.insert(DicomTag(0x0038, 0x0061));                      // Issuer of Service Episode ID
+    removals_.insert(DicomTag(0x0038, 0x0062));                      // Service Episode Description
+    removals_.insert(DicomTag(0x0038, 0x0300));                      // Current Patient Location
+    removals_.insert(DicomTag(0x0038, 0x0400));                      // Patient's Institution Residence
+    removals_.insert(DicomTag(0x0038, 0x0500));                      // Patient State
+    removals_.insert(DicomTag(0x0038, 0x4000));                      // Visit Comments
+    removals_.insert(DicomTag(0x0040, 0x0001));                      // Scheduled Station AE Title
+    removals_.insert(DicomTag(0x0040, 0x0002));                      // Scheduled Procedure Step Start Date
+    removals_.insert(DicomTag(0x0040, 0x0003));                      // Scheduled Procedure Step Start Time
+    removals_.insert(DicomTag(0x0040, 0x0004));                      // Scheduled Procedure Step End Date
+    removals_.insert(DicomTag(0x0040, 0x0005));                      // Scheduled Procedure Step End Time
+    removals_.insert(DicomTag(0x0040, 0x0006));                      // Scheduled Performing Physician Name
+    removals_.insert(DicomTag(0x0040, 0x0007));                      // Scheduled Procedure Step Description
+    removals_.insert(DicomTag(0x0040, 0x000b));                      // Scheduled Performing Physician Identification Sequence
+    removals_.insert(DicomTag(0x0040, 0x0010));                      // Scheduled Station Name
+    removals_.insert(DicomTag(0x0040, 0x0011));                      // Scheduled Procedure Step Location
+    removals_.insert(DicomTag(0x0040, 0x0012));                      // Pre-Medication
+    removals_.insert(DicomTag(0x0040, 0x0241));                      // Performed Station AE Title
+    removals_.insert(DicomTag(0x0040, 0x0242));                      // Performed Station Name
+    removals_.insert(DicomTag(0x0040, 0x0243));                      // Performed Location
+    removals_.insert(DicomTag(0x0040, 0x0244));                      // Performed Procedure Step Start Date
+    removals_.insert(DicomTag(0x0040, 0x0245));                      // Performed Procedure Step Start Time
+    removals_.insert(DicomTag(0x0040, 0x0250));                      // Performed Procedure Step End Date
+    removals_.insert(DicomTag(0x0040, 0x0251));                      // Performed Procedure Step End Time
+    removals_.insert(DicomTag(0x0040, 0x0253));                      // Performed Procedure Step ID
+    removals_.insert(DicomTag(0x0040, 0x0254));                      // Performed Procedure Step Description
+    removals_.insert(DicomTag(0x0040, 0x0275));                      // Request Attributes Sequence
+    removals_.insert(DicomTag(0x0040, 0x0280));                      // Comments on the Performed Procedure Step
+    removals_.insert(DicomTag(0x0040, 0x0555));                      // Acquisition Context Sequence
+    removals_.insert(DicomTag(0x0040, 0x1001));                      // Requested Procedure ID
+    removals_.insert(DicomTag(0x0040, 0x1004));                      // Patient Transport Arrangements
+    removals_.insert(DicomTag(0x0040, 0x1005));                      // Requested Procedure Location
+    removals_.insert(DicomTag(0x0040, 0x1010));                      // Names of Intended Recipient of Results
+    removals_.insert(DicomTag(0x0040, 0x1011));                      // Intended Recipients of Results Identification Sequence
+    removals_.insert(DicomTag(0x0040, 0x1102));                      // Person Address
+    removals_.insert(DicomTag(0x0040, 0x1103));                      // Person's Telephone Numbers
+    removals_.insert(DicomTag(0x0040, 0x1104));                      // Person's Telecom Information
+    removals_.insert(DicomTag(0x0040, 0x1400));                      // Requested Procedure Comments
+    removals_.insert(DicomTag(0x0040, 0x2001));                      // Reason for the Imaging Service Request
+    removals_.insert(DicomTag(0x0040, 0x2008));                      // Order Entered By
+    removals_.insert(DicomTag(0x0040, 0x2009));                      // Order Enterer Location
+    removals_.insert(DicomTag(0x0040, 0x2010));                      // Order Callback Phone Number
+    removals_.insert(DicomTag(0x0040, 0x2011));                      // Order Callback Telecom Information
+    removals_.insert(DicomTag(0x0040, 0x2400));                      // Imaging Service Request Comments
+    removals_.insert(DicomTag(0x0040, 0x3001));                      // Confidentiality Constraint on Patient Data Description
+    removals_.insert(DicomTag(0x0040, 0x4005));                      // Scheduled Procedure Step Start DateTime
+    removals_.insert(DicomTag(0x0040, 0x4010));                      // Scheduled Procedure Step Modification DateTime
+    removals_.insert(DicomTag(0x0040, 0x4011));                      // Expected Completion DateTime
+    removals_.insert(DicomTag(0x0040, 0x4023));   /* TODO UID */     // Referenced General Purpose Scheduled Procedure Step Transaction UID
+    removals_.insert(DicomTag(0x0040, 0x4025));                      // Scheduled Station Name Code Sequence
+    removals_.insert(DicomTag(0x0040, 0x4027));                      // Scheduled Station Geographic Location Code Sequence
+    removals_.insert(DicomTag(0x0040, 0x4028));                      // Performed Station Name Code Sequence
+    removals_.insert(DicomTag(0x0040, 0x4030));                      // Performed Station Geographic Location Code Sequence
+    removals_.insert(DicomTag(0x0040, 0x4034));                      // Scheduled Human Performers Sequence
+    removals_.insert(DicomTag(0x0040, 0x4035));                      // Actual Human Performers Sequence
+    removals_.insert(DicomTag(0x0040, 0x4036));                      // Human Performers Organization
+    removals_.insert(DicomTag(0x0040, 0x4037));                      // Human Performers Name
+    removals_.insert(DicomTag(0x0040, 0x4050));                      // Performed Procedure Step Start DateTime
+    removals_.insert(DicomTag(0x0040, 0x4051));                      // Performed Procedure Step End DateTime
+    removals_.insert(DicomTag(0x0040, 0x4052));                      // Procedure Step Cancellation DateTime
+    removals_.insert(DicomTag(0x0040, 0xa027));                      // Verifying Organization
+    removals_.insert(DicomTag(0x0040, 0xa078));                      // Author Observer Sequence
+    removals_.insert(DicomTag(0x0040, 0xa07a));                      // Participant Sequence
+    removals_.insert(DicomTag(0x0040, 0xa07c));                      // Custodial Organization Sequence
+    removals_.insert(DicomTag(0x0040, 0xa124));   /* TODO UID */     // UID
+    removals_.insert(DicomTag(0x0040, 0xa171));   /* TODO UID */     // Observation UID
+    removals_.insert(DicomTag(0x0040, 0xa172));   /* TODO UID */     // Referenced Observation UID (Trial)
+    removals_.insert(DicomTag(0x0040, 0xa192));                      // Observation Date (Trial)
+    removals_.insert(DicomTag(0x0040, 0xa193));                      // Observation Time (Trial)
+    removals_.insert(DicomTag(0x0040, 0xa307));                      // Current Observer (Trial)
+    removals_.insert(DicomTag(0x0040, 0xa352));                      // Verbal Source (Trial)
+    removals_.insert(DicomTag(0x0040, 0xa353));                      // Address (Trial)
+    removals_.insert(DicomTag(0x0040, 0xa354));                      // Telephone Number (Trial)
+    removals_.insert(DicomTag(0x0040, 0xa358));                      // Verbal Source Identifier Code Sequence (Trial)
+    removals_.insert(DicomTag(0x0040, 0xa402));   /* TODO UID */     // Observation Subject UID (Trial)
+    removals_.insert(DicomTag(0x0040, 0xa730));                      // Content Sequence
+    removals_.insert(DicomTag(0x0040, 0xdb0c));   /* TODO UID */     // Template Extension Organization UID
+    removals_.insert(DicomTag(0x0040, 0xdb0d));   /* TODO UID */     // Template Extension Creator UID
+    removals_.insert(DicomTag(0x0062, 0x0021));   /* TODO UID */     // Tracking UID
+    removals_.insert(DicomTag(0x0070, 0x0086));                      // Content Creator's Identification Code Sequence
+    removals_.insert(DicomTag(0x0070, 0x031a));   /* TODO UID */     // Fiducial UID
+    removals_.insert(DicomTag(0x0070, 0x1101));   /* TODO UID */     // Presentation Display Collection UID
+    removals_.insert(DicomTag(0x0070, 0x1102));   /* TODO UID */     // Presentation Sequence Collection UID
+    removals_.insert(DicomTag(0x0088, 0x0140));   /* TODO UID */     // Storage Media File-set UID
+    removals_.insert(DicomTag(0x0088, 0x0200));                      // Icon Image Sequence(see Note 12)
+    removals_.insert(DicomTag(0x0088, 0x0904));                      // Topic Title
+    removals_.insert(DicomTag(0x0088, 0x0906));                      // Topic Subject
+    removals_.insert(DicomTag(0x0088, 0x0910));                      // Topic Author
+    removals_.insert(DicomTag(0x0088, 0x0912));                      // Topic Keywords
+    removals_.insert(DicomTag(0x0400, 0x0100));                      // Digital Signature UID
+    removals_.insert(DicomTag(0x0400, 0x0402));                      // Referenced Digital Signature Sequence
+    removals_.insert(DicomTag(0x0400, 0x0403));                      // Referenced SOP Instance MAC Sequence
+    removals_.insert(DicomTag(0x0400, 0x0404));                      // MAC
+    removals_.insert(DicomTag(0x0400, 0x0550));                      // Modified Attributes Sequence
+    removals_.insert(DicomTag(0x0400, 0x0561));                      // Original Attributes Sequence
+    removals_.insert(DicomTag(0x2030, 0x0020));                      // Text String
+    removals_.insert(DicomTag(0x3006, 0x0024));   /* TODO UID */     // Referenced Frame of Reference UID
+    removals_.insert(DicomTag(0x3006, 0x00c2));   /* TODO UID */     // Related Frame of Reference UID
+    removals_.insert(DicomTag(0x3008, 0x0105));                      // Source Serial Number
+    removals_.insert(DicomTag(0x300a, 0x0013));   /* TODO UID */     // Dose Reference UID
+    removals_.insert(DicomTag(0x300c, 0x0113));                      // Reason for Omission Description
+    removals_.insert(DicomTag(0x300e, 0x0008));   /* X/Z */          // Reviewer Name
+    removals_.insert(DicomTag(0x4000, 0x0010));                      // Arbitrary
+    removals_.insert(DicomTag(0x4000, 0x4000));                      // Text Comments
+    removals_.insert(DicomTag(0x4008, 0x0042));                      // Results ID Issuer
+    removals_.insert(DicomTag(0x4008, 0x0102));                      // Interpretation Recorder
+    removals_.insert(DicomTag(0x4008, 0x010a));                      // Interpretation Transcriber
+    removals_.insert(DicomTag(0x4008, 0x010b));                      // Interpretation Text
+    removals_.insert(DicomTag(0x4008, 0x010c));                      // Interpretation Author
+    removals_.insert(DicomTag(0x4008, 0x0111));                      // Interpretation Approver Sequence
+    removals_.insert(DicomTag(0x4008, 0x0114));                      // Physician Approving Interpretation
+    removals_.insert(DicomTag(0x4008, 0x0115));                      // Interpretation Diagnosis Description
+    removals_.insert(DicomTag(0x4008, 0x0118));                      // Results Distribution List Sequence
+    removals_.insert(DicomTag(0x4008, 0x0119));                      // Distribution Name
+    removals_.insert(DicomTag(0x4008, 0x011a));                      // Distribution Address
+    removals_.insert(DicomTag(0x4008, 0x0202));                      // Interpretation ID Issuer
+    removals_.insert(DicomTag(0x4008, 0x0300));                      // Impressions
+    removals_.insert(DicomTag(0x4008, 0x4000));                      // Results Comments
+    removals_.insert(DicomTag(0xfffa, 0xfffa));                      // Digital Signatures Sequence
+    removals_.insert(DicomTag(0xfffc, 0xfffc));                      // Data Set Trailing Padding
+    
+    // Set the DeidentificationMethod tag
+    ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2017c);
+  }
+  
+
+  void DicomModification::SetupAnonymization(DicomVersion version)
+  {
+    removals_.clear();
+    clearings_.clear();
+    ClearReplacements();
+    removePrivateTags_ = true;
+    level_ = ResourceType_Patient;
+    uidMap_.clear();
+    privateTagsToKeep_.clear();
+
+    switch (version)
+    {
+      case DicomVersion_2008:
+        SetupAnonymization2008();
+        break;
+
+      case DicomVersion_2017c:
+        SetupAnonymization2017c();
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    // Set the PatientIdentityRemoved tag
+    ReplaceInternal(DicomTag(0x0012, 0x0062), "YES");
+
+    // (*) Choose a random patient name and ID
+    std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient);
+    ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId);
+    ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId);
+  }
+
+  void DicomModification::Apply(ParsedDicomFile& toModify)
+  {
+    // Check the request
+    assert(ResourceType_Patient + 1 == ResourceType_Study &&
+           ResourceType_Study + 1 == ResourceType_Series &&
+           ResourceType_Series + 1 == ResourceType_Instance);
+
+    if (IsRemoved(DICOM_TAG_PATIENT_ID) ||
+        IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) ||
+        IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) ||
+        IsRemoved(DICOM_TAG_SOP_INSTANCE_UID))
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+    
+
+    // Sanity checks at the patient level
+    if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID))
+    {
+      LOG(ERROR) << "When modifying a patient, her PatientID is required to be modified";
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    if (!allowManualIdentifiers_)
+    {
+      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a patient, the StudyInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+
+      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a patient, the SeriesInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+
+      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a patient, the SopInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+    }
+
+
+    // Sanity checks at the study level
+    if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID))
+    {
+      LOG(ERROR) << "When modifying a study, the parent PatientID cannot be manually modified";
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    if (!allowManualIdentifiers_)
+    {
+      if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a study, the SeriesInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+
+      if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a study, the SopInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+    }
+
+
+    // Sanity checks at the series level
+    if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID))
+    {
+      LOG(ERROR) << "When modifying a series, the parent PatientID cannot be manually modified";
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
+    {
+      LOG(ERROR) << "When modifying a series, the parent StudyInstanceUID cannot be manually modified";
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    if (!allowManualIdentifiers_)
+    {
+      if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
+      {
+        LOG(ERROR) << "When modifying a series, the SopInstanceUID cannot be manually modified";
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+    }
+
+
+    // Sanity checks at the instance level
+    if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID))
+    {
+      LOG(ERROR) << "When modifying an instance, the parent PatientID cannot be manually modified";
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
+    {
+      LOG(ERROR) << "When modifying an instance, the parent StudyInstanceUID cannot be manually modified";
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
+    {
+      LOG(ERROR) << "When modifying an instance, the parent SeriesInstanceUID cannot be manually modified";
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+
+    // (1) Remove the private tags, if need be
+    if (removePrivateTags_)
+    {
+      toModify.RemovePrivateTags(privateTagsToKeep_);
+    }
+
+    // (2) Clear the tags specified by the user
+    for (SetOfTags::const_iterator it = clearings_.begin(); 
+         it != clearings_.end(); ++it)
+    {
+      toModify.Clear(*it, true /* only clear if the tag exists in the original file */);
+    }
+
+    // (3) Remove the tags specified by the user
+    for (SetOfTags::const_iterator it = removals_.begin(); 
+         it != removals_.end(); ++it)
+    {
+      toModify.Remove(*it);
+    }
+
+    // (4) Replace the tags
+    for (Replacements::const_iterator it = replacements_.begin(); 
+         it != replacements_.end(); ++it)
+    {
+      toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent);
+    }
+
+    // (5) Update the DICOM identifiers
+    if (level_ <= ResourceType_Study &&
+        !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
+    {
+      if (keepStudyInstanceUid_)
+      {
+        LOG(WARNING) << "Modifying a study while keeping its original StudyInstanceUID: This should be avoided!";
+      }
+      else
+      {
+        MapDicomIdentifier(toModify, ResourceType_Study);
+      }
+    }
+
+    if (level_ <= ResourceType_Series &&
+        !IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
+    {
+      if (keepSeriesInstanceUid_)
+      {
+        LOG(WARNING) << "Modifying a series while keeping its original SeriesInstanceUID: This should be avoided!";
+      }
+      else
+      {
+        MapDicomIdentifier(toModify, ResourceType_Series);
+      }
+    }
+
+    if (level_ <= ResourceType_Instance &&  // Always true
+        !IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
+    {
+      MapDicomIdentifier(toModify, ResourceType_Instance);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DicomModification.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,137 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ParsedDicomFile.h"
+
+namespace Orthanc
+{
+  class DicomModification : public boost::noncopyable
+  {
+    /**
+     * Process:
+     * (1) Remove private tags
+     * (2) Remove tags specified by the user
+     * (3) Replace tags
+     **/
+
+  private:
+    typedef std::set<DicomTag> SetOfTags;
+    typedef std::map<DicomTag, Json::Value*> Replacements;
+    typedef std::map< std::pair<ResourceType, std::string>, std::string>  UidMap;
+
+    SetOfTags removals_;
+    SetOfTags clearings_;
+    Replacements replacements_;
+    bool removePrivateTags_;
+    ResourceType level_;
+    UidMap uidMap_;
+    SetOfTags privateTagsToKeep_;
+    bool allowManualIdentifiers_;
+    bool keepStudyInstanceUid_;
+    bool keepSeriesInstanceUid_;
+
+    void MapDicomIdentifier(ParsedDicomFile& dicom,
+                            ResourceType level);
+
+    void MarkNotOrthancAnonymization();
+
+    void ClearReplacements();
+
+    bool CancelReplacement(const DicomTag& tag);
+
+    void ReplaceInternal(const DicomTag& tag,
+                         const Json::Value& value);
+
+    void SetupAnonymization2008();
+
+    void SetupAnonymization2017c();
+
+  public:
+    DicomModification();
+
+    ~DicomModification();
+
+    void Keep(const DicomTag& tag);
+
+    void Remove(const DicomTag& tag);
+
+    // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization)
+    void Clear(const DicomTag& tag);
+
+    bool IsRemoved(const DicomTag& tag) const;
+
+    bool IsCleared(const DicomTag& tag) const;
+
+    // "safeForAnonymization" tells Orthanc that this replacement does
+    // not break the anonymization process it implements (for internal use only)
+    void Replace(const DicomTag& tag,
+                 const Json::Value& value,   // Encoded using UTF-8
+                 bool safeForAnonymization);
+
+    bool IsReplaced(const DicomTag& tag) const;
+
+    const Json::Value& GetReplacement(const DicomTag& tag) const;
+
+    std::string GetReplacementAsString(const DicomTag& tag) const;
+
+    void SetRemovePrivateTags(bool removed);
+
+    bool ArePrivateTagsRemoved() const
+    {
+      return removePrivateTags_;
+    }
+
+    void SetLevel(ResourceType level);
+
+    ResourceType GetLevel() const
+    {
+      return level_;
+    }
+
+    void SetupAnonymization(DicomVersion version);
+
+    void Apply(ParsedDicomFile& toModify);
+
+    void SetAllowManualIdentifiers(bool check)
+    {
+      allowManualIdentifiers_ = check;
+    }
+
+    bool AreAllowManualIdentifiers() const
+    {
+      return allowManualIdentifiers_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/FromDcmtkBridge.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,2075 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * 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 "FromDcmtkBridge.h"
+#include "ToDcmtkBridge.h"
+#include "../Logging.h"
+#include "../SystemToolbox.h"
+#include "../Toolbox.h"
+#include "../TemporaryFile.h"
+#include "../OrthancException.h"
+
+#include <list>
+#include <limits>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcdicent.h>
+#include <dcmtk/dcmdata/dcdict.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcostrmb.h>
+#include <dcmtk/dcmdata/dcpixel.h>
+#include <dcmtk/dcmdata/dcuid.h>
+#include <dcmtk/dcmdata/dcistrmb.h>
+
+#include <dcmtk/dcmdata/dcvrae.h>
+#include <dcmtk/dcmdata/dcvras.h>
+#include <dcmtk/dcmdata/dcvrat.h>
+#include <dcmtk/dcmdata/dcvrcs.h>
+#include <dcmtk/dcmdata/dcvrda.h>
+#include <dcmtk/dcmdata/dcvrds.h>
+#include <dcmtk/dcmdata/dcvrdt.h>
+#include <dcmtk/dcmdata/dcvrfd.h>
+#include <dcmtk/dcmdata/dcvrfl.h>
+#include <dcmtk/dcmdata/dcvris.h>
+#include <dcmtk/dcmdata/dcvrlo.h>
+#include <dcmtk/dcmdata/dcvrlt.h>
+#include <dcmtk/dcmdata/dcvrpn.h>
+#include <dcmtk/dcmdata/dcvrsh.h>
+#include <dcmtk/dcmdata/dcvrsl.h>
+#include <dcmtk/dcmdata/dcvrss.h>
+#include <dcmtk/dcmdata/dcvrst.h>
+#include <dcmtk/dcmdata/dcvrtm.h>
+#include <dcmtk/dcmdata/dcvrui.h>
+#include <dcmtk/dcmdata/dcvrul.h>
+#include <dcmtk/dcmdata/dcvrus.h>
+#include <dcmtk/dcmdata/dcvrut.h>
+
+#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
+#  include <EmbeddedResources.h>
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+#  include <dcmtk/dcmjpeg/djdecode.h>
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+#  include <dcmtk/dcmjpls/djdecode.h>
+#endif
+
+
+namespace Orthanc
+{
+  static inline uint16_t GetCharValue(char c)
+  {
+    if (c >= '0' && c <= '9')
+      return c - '0';
+    else if (c >= 'a' && c <= 'f')
+      return c - 'a' + 10;
+    else if (c >= 'A' && c <= 'F')
+      return c - 'A' + 10;
+    else
+      return 0;
+  }
+
+  static inline uint16_t GetTagValue(const char* c)
+  {
+    return ((GetCharValue(c[0]) << 12) + 
+            (GetCharValue(c[1]) << 8) + 
+            (GetCharValue(c[2]) << 4) + 
+            GetCharValue(c[3]));
+  }
+
+
+#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
+  static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary,
+                                     EmbeddedResources::FileResourceId resource)
+  {
+    std::string content;
+    EmbeddedResources::GetFileResource(content, resource);
+
+    TemporaryFile tmp;
+    tmp.Write(content);
+
+    if (!dictionary.loadDictionary(tmp.GetPath().c_str()))
+    {
+      LOG(ERROR) << "Cannot read embedded dictionary. Under Windows, make sure that " 
+                 << "your TEMP directory does not contain special characters.";
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+                             
+#else
+  static void LoadExternalDictionary(DcmDataDictionary& dictionary,
+                                     const std::string& directory,
+                                     const std::string& filename)
+  {
+    boost::filesystem::path p = directory;
+    p = p / filename;
+
+    LOG(WARNING) << "Loading the external DICOM dictionary " << p;
+
+    if (!dictionary.loadDictionary(p.string().c_str()))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+#endif
+
+
+  namespace
+  {
+    class DictionaryLocker
+    {
+    private:
+      DcmDataDictionary& dictionary_;
+
+    public:
+      DictionaryLocker() : dictionary_(dcmDataDict.wrlock())
+      {
+      }
+
+      ~DictionaryLocker()
+      {
+        dcmDataDict.unlock();
+      }
+
+      DcmDataDictionary& operator*()
+      {
+        return dictionary_;
+      }
+
+      DcmDataDictionary* operator->()
+      {
+        return &dictionary_;
+      }
+    };
+  }
+
+
+  void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary)
+  {
+    LOG(INFO) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER;
+    
+    {
+      DictionaryLocker locker;
+
+      locker->clear();
+
+#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
+      LOG(WARNING) << "Loading the embedded dictionaries";
+      /**
+       * Do not load DICONDE dictionary, it breaks the other tags. The
+       * command "strace storescu 2>&1 |grep dic" shows that DICONDE
+       * dictionary is not loaded by storescu.
+       **/
+      //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE);
+
+      LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM);
+
+      if (loadPrivateDictionary)
+      {
+        LOG(INFO) << "Loading the embedded dictionary of private tags";
+        LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE);
+      }
+      else
+      {
+        LOG(INFO) << "The dictionary of private tags has not been loaded";
+      }
+
+#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+      std::string path = DCMTK_DICTIONARY_DIR;
+
+      const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
+      if (env != NULL)
+      {
+        path = std::string(env);
+      }
+
+      LoadExternalDictionary(*locker, path, "dicom.dic");
+
+      if (loadPrivateDictionary)
+      {
+        LoadExternalDictionary(*locker, path, "private.dic");
+      }
+      else
+      {
+        LOG(INFO) << "The dictionary of private tags has not been loaded";
+      }
+
+#else
+#error Support your platform here
+#endif
+    }
+
+    /* make sure data dictionary is loaded */
+    if (!dcmDataDict.isDictionaryLoaded())
+    {
+      LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE;
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    {
+      // Test the dictionary with a simple DICOM tag
+      DcmTag key(0x0010, 0x1030); // This is PatientWeight
+      if (key.getEVR() != EVR_DS)
+      {
+        LOG(ERROR) << "The DICOM dictionary has not been correctly read";
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag,
+                                              ValueRepresentation vr,
+                                              const std::string& name,
+                                              unsigned int minMultiplicity,
+                                              unsigned int maxMultiplicity,
+                                              const std::string& privateCreator)
+  {
+    if (minMultiplicity < 1)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    bool arbitrary = false;
+    if (maxMultiplicity == 0)
+    {
+      maxMultiplicity = DcmVariableVM;
+      arbitrary = true;
+    }
+    else if (maxMultiplicity < minMultiplicity)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    
+    DcmEVR evr = ToDcmtkBridge::Convert(vr);
+
+    LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(evr).getValidVRName()) << " " 
+              << name << " (multiplicity: " << minMultiplicity << "-" 
+              << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")";
+
+    std::auto_ptr<DcmDictEntry>  entry;
+    if (privateCreator.empty())
+    {
+      if (tag.GetGroup() % 2 == 1)
+      {
+        char buf[128];
+        sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), "
+                "but no private creator was associated with it", 
+                tag.GetGroup(), tag.GetElement());
+        LOG(WARNING) << buf;
+      }
+
+      entry.reset(new DcmDictEntry(tag.GetGroup(),
+                                   tag.GetElement(),
+                                   evr, name.c_str(),
+                                   static_cast<int>(minMultiplicity),
+                                   static_cast<int>(maxMultiplicity),
+                                   NULL    /* version */,
+                                   OFTrue  /* doCopyString */,
+                                   NULL    /* private creator */));
+    }
+    else
+    {
+      // "Private Data Elements have an odd Group Number that is not
+      // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or
+      // (FFFF,eeee)."
+      if (tag.GetGroup() % 2 == 0 /* even */ ||
+          tag.GetGroup() == 0x0001 ||
+          tag.GetGroup() == 0x0003 ||
+          tag.GetGroup() == 0x0005 ||
+          tag.GetGroup() == 0x0007 ||
+          tag.GetGroup() == 0xffff)
+      {
+        char buf[128];
+        sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009",
+                tag.GetGroup(), tag.GetElement());
+        LOG(ERROR) << buf;
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      entry.reset(new DcmDictEntry(tag.GetGroup(),
+                                   tag.GetElement(),
+                                   evr, name.c_str(),
+                                   static_cast<int>(minMultiplicity),
+                                   static_cast<int>(maxMultiplicity),
+                                   "private" /* version */,
+                                   OFTrue    /* doCopyString */,
+                                   privateCreator.c_str()));
+    }
+
+    entry->setGroupRangeRestriction(DcmDictRange_Unspecified);
+    entry->setElementRangeRestriction(DcmDictRange_Unspecified);
+
+    {
+      DictionaryLocker locker;
+
+      if (locker->findEntry(name.c_str()))
+      {
+        LOG(ERROR) << "Cannot register two tags with the same symbolic name \"" << name << "\"";
+        throw OrthancException(ErrorCode_AlreadyExistingTag);
+      }
+
+      locker->addEntry(entry.release());
+    }
+  }
+
+
+  Encoding FromDcmtkBridge::DetectEncoding(DcmItem& dataset,
+                                           Encoding defaultEncoding)
+  {
+    Encoding encoding = defaultEncoding;
+
+    OFString tmp;
+    if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good())
+    {
+      std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str()));
+
+      if (characterSet.empty())
+      {
+        // Empty specific character set tag: Use the default encoding
+      }
+      else if (GetDicomEncoding(encoding, characterSet.c_str()))
+      {
+        // The specific character set is supported by the Orthanc core
+      }
+      else
+      {
+        LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet
+                     << ", fallback to ASCII (remove all special characters)";
+        encoding = Encoding_Ascii;
+      }
+    }
+    else
+    {
+      // No specific character set tag: Use the default encoding
+    }
+
+    return encoding;
+  }
+
+
+  void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, 
+                                            DcmItem& dataset,
+                                            unsigned int maxStringLength,
+                                            Encoding defaultEncoding)
+  {
+    Encoding encoding = DetectEncoding(dataset, defaultEncoding);
+
+    target.Clear();
+    for (unsigned long i = 0; i < dataset.card(); i++)
+    {
+      DcmElement* element = dataset.getElement(i);
+      if (element && element->isLeaf())
+      {
+        target.SetValue(element->getTag().getGTag(),
+                        element->getTag().getETag(),
+                        ConvertLeafElement(*element, DicomToJsonFlags_Default, maxStringLength, encoding));
+      }
+    }
+  }
+
+
+  DicomTag FromDcmtkBridge::Convert(const DcmTag& tag)
+  {
+    return DicomTag(tag.getGTag(), tag.getETag());
+  }
+
+
+  DicomTag FromDcmtkBridge::GetTag(const DcmElement& element)
+  {
+    return DicomTag(element.getGTag(), element.getETag());
+  }
+
+
+  DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element,
+                                                  DicomToJsonFlags flags,
+                                                  unsigned int maxStringLength,
+                                                  Encoding encoding)
+  {
+    if (!element.isLeaf())
+    {
+      // This function is only applicable to leaf elements
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    char *c = NULL;
+    if (element.isaString() &&
+        element.getString(c).good())
+    {
+      if (c == NULL)  // This case corresponds to the empty string
+      {
+        return new DicomValue("", false);
+      }
+      else
+      {
+        std::string s(c);
+        std::string utf8 = Toolbox::ConvertToUtf8(s, encoding);
+
+        if (maxStringLength != 0 &&
+            utf8.size() > maxStringLength)
+        {
+          return new DicomValue;  // Too long, create a NULL value
+        }
+        else
+        {
+          return new DicomValue(utf8, false);
+        }
+      }
+    }
+
+
+    if (element.getVR() == EVR_UN)
+    {
+      // Unknown value representation: Lookup in the dictionary. This
+      // is notably the case for private tags registered with the
+      // "Dictionary" configuration option.
+      DictionaryLocker locker;
+      
+      const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), 
+                                                    element.getTag().getPrivateCreator());
+      if (entry != NULL && 
+          entry->getVR().isaString())
+      {
+        Uint8* data = NULL;
+
+        // At (*), we do not try and convert to UTF-8, as nothing says
+        // the encoding of the private tag is the same as that of the
+        // remaining of the DICOM dataset. Only go for ASCII strings.
+
+        if (element.getUint8Array(data) == EC_Normal &&
+            Toolbox::IsAsciiString(data, element.getLength()))   // (*)
+        {
+          if (data == NULL)
+          {
+            return new DicomValue("", false);   // Empty string
+          }
+          else if (maxStringLength != 0 &&
+                   element.getLength() > maxStringLength)
+          {
+            return new DicomValue;  // Too long, create a NULL value
+          }
+          else
+          {
+            std::string s(reinterpret_cast<const char*>(data), element.getLength());
+            return new DicomValue(s, false);
+          }
+        }
+      }
+    }
+
+
+    try
+    {
+      // http://support.dcmtk.org/docs/dcvr_8h-source.html
+      switch (element.getVR())
+      {
+
+        /**
+         * Deal with binary data (including PixelData).
+         **/
+
+        case EVR_OB:  // other byte
+        case EVR_OF:  // other float
+        case EVR_OW:  // other word
+        case EVR_UN:  // unknown value representation
+        case EVR_ox:  // OB or OW depending on context
+        case EVR_DS:  // decimal string
+        case EVR_IS:  // integer string
+        case EVR_AS:  // age string
+        case EVR_DA:  // date string
+        case EVR_DT:  // date time string
+        case EVR_TM:  // time string
+        case EVR_AE:  // application entity title
+        case EVR_CS:  // code string
+        case EVR_SH:  // short string
+        case EVR_LO:  // long string
+        case EVR_ST:  // short text
+        case EVR_LT:  // long text
+        case EVR_UT:  // unlimited text
+        case EVR_PN:  // person name
+        case EVR_UI:  // unique identifier
+        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
+        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
+        {
+          if (!(flags & DicomToJsonFlags_ConvertBinaryToNull))
+          {
+            Uint8* data = NULL;
+            if (element.getUint8Array(data) == EC_Normal)
+            {
+              return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true);
+            }
+          }
+
+          return new DicomValue;
+        }
+    
+        /**
+         * Numeric types
+         **/ 
+      
+        case EVR_SL:  // signed long
+        {
+          Sint32 f;
+          if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+        case EVR_SS:  // signed short
+        {
+          Sint16 f;
+          if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+        case EVR_UL:  // unsigned long
+        {
+          Uint32 f;
+          if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+        case EVR_US:  // unsigned short
+        {
+          Uint16 f;
+          if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+        case EVR_FL:  // float single-precision
+        {
+          Float32 f;
+          if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+        case EVR_FD:  // float double-precision
+        {
+          Float64 f;
+          if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good())
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
+          else
+            return new DicomValue;
+        }
+
+
+        /**
+         * Attribute tag.
+         **/
+
+        case EVR_AT:
+        {
+          DcmTagKey tag;
+          if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good())
+          {
+            DicomTag t(tag.getGroup(), tag.getElement());
+            return new DicomValue(t.Format(), false);
+          }
+          else
+          {
+            return new DicomValue;
+          }
+        }
+
+
+        /**
+         * Sequence types, should never occur at this point because of
+         * "element.isLeaf()".
+         **/
+
+        case EVR_SQ:  // sequence of items
+          return new DicomValue;
+
+
+          /**
+           * Internal to DCMTK.
+           **/ 
+
+        case EVR_xs:  // SS or US depending on context
+        case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
+        case EVR_na:  // na="not applicable", for data which has no VR
+        case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
+        case EVR_item:  // used internally for items
+        case EVR_metainfo:  // used internally for meta info datasets
+        case EVR_dataset:  // used internally for datasets
+        case EVR_fileFormat:  // used internally for DICOM files
+        case EVR_dicomDir:  // used internally for DICOMDIR objects
+        case EVR_dirRecord:  // used internally for DICOMDIR records
+        case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
+        case EVR_pixelItem:  // used internally for pixel items in a compressed image
+        case EVR_PixelData:  // used internally for uncompressed pixeld data
+        case EVR_OverlayData:  // used internally for overlay data
+          return new DicomValue;
+
+
+          /**
+           * Default case.
+           **/ 
+
+        default:
+          return new DicomValue;
+      }
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      return new DicomValue;
+    }
+    catch (std::bad_cast)
+    {
+      return new DicomValue;
+    }
+  }
+
+
+  static Json::Value& PrepareNode(Json::Value& parent,
+                                  DcmElement& element,
+                                  DicomToJsonFormat format)
+  {
+    assert(parent.type() == Json::objectValue);
+
+    DicomTag tag(FromDcmtkBridge::GetTag(element));
+    const std::string formattedTag = tag.Format();
+
+    if (format == DicomToJsonFormat_Short)
+    {
+      parent[formattedTag] = Json::nullValue;
+      return parent[formattedTag];
+    }
+
+    // This code gives access to the name of the private tags
+    std::string tagName = FromDcmtkBridge::GetTagName(element);
+    
+    switch (format)
+    {
+      case DicomToJsonFormat_Human:
+        parent[tagName] = Json::nullValue;
+        return parent[tagName];
+
+      case DicomToJsonFormat_Full:
+      {
+        parent[formattedTag] = Json::objectValue;
+        Json::Value& node = parent[formattedTag];
+
+        if (element.isLeaf())
+        {
+          node["Name"] = tagName;
+
+          if (element.getTag().getPrivateCreator() != NULL)
+          {
+            node["PrivateCreator"] = element.getTag().getPrivateCreator();
+          }
+
+          return node;
+        }
+        else
+        {
+          node["Name"] = tagName;
+          node["Type"] = "Sequence";
+          node["Value"] = Json::nullValue;
+          return node["Value"];
+        }
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  static void LeafValueToJson(Json::Value& target,
+                              const DicomValue& value,
+                              DicomToJsonFormat format,
+                              DicomToJsonFlags flags,
+                              unsigned int maxStringLength)
+  {
+    Json::Value* targetValue = NULL;
+    Json::Value* targetType = NULL;
+
+    switch (format)
+    {
+      case DicomToJsonFormat_Short:
+      case DicomToJsonFormat_Human:
+      {
+        assert(target.type() == Json::nullValue);
+        targetValue = &target;
+        break;
+      }      
+
+      case DicomToJsonFormat_Full:
+      {
+        assert(target.type() == Json::objectValue);
+        target["Value"] = Json::nullValue;
+        target["Type"] = Json::nullValue;
+        targetType = &target["Type"];
+        targetValue = &target["Value"];
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(targetValue != NULL);
+    assert(targetValue->type() == Json::nullValue);
+    assert(targetType == NULL || targetType->type() == Json::nullValue);
+
+    if (value.IsNull())
+    {
+      if (targetType != NULL)
+      {
+        *targetType = "Null";
+      }
+    }
+    else if (value.IsBinary())
+    {
+      if (flags & DicomToJsonFlags_ConvertBinaryToAscii)
+      {
+        *targetValue = Toolbox::ConvertToAscii(value.GetContent());
+      }
+      else
+      {
+        std::string s;
+        value.FormatDataUriScheme(s);
+        *targetValue = s;
+      }
+
+      if (targetType != NULL)
+      {
+        *targetType = "Binary";
+      }
+    }
+    else if (maxStringLength == 0 ||
+             value.GetContent().size() <= maxStringLength)
+    {
+      *targetValue = value.GetContent();
+
+      if (targetType != NULL)
+      {
+        *targetType = "String";
+      }
+    }
+    else
+    {
+      if (targetType != NULL)
+      {
+        *targetType = "TooLong";
+      }
+    }
+  }                              
+
+
+  void FromDcmtkBridge::ElementToJson(Json::Value& parent,
+                                      DcmElement& element,
+                                      DicomToJsonFormat format,
+                                      DicomToJsonFlags flags,
+                                      unsigned int maxStringLength,
+                                      Encoding encoding)
+  {
+    if (parent.type() == Json::nullValue)
+    {
+      parent = Json::objectValue;
+    }
+
+    assert(parent.type() == Json::objectValue);
+    Json::Value& target = PrepareNode(parent, element, format);
+
+    if (element.isLeaf())
+    {
+      // The "0" below lets "LeafValueToJson()" take care of "TooLong" values
+      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, 0, encoding));
+      LeafValueToJson(target, *v, format, flags, maxStringLength);
+    }
+    else
+    {
+      assert(target.type() == Json::nullValue);
+      target = Json::arrayValue;
+
+      // "All subclasses of DcmElement except for DcmSequenceOfItems
+      // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
+      // etc. are not." The following dynamic_cast is thus OK.
+      DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);
+
+      for (unsigned long i = 0; i < sequence.card(); i++)
+      {
+        DcmItem* child = sequence.getItem(i);
+        Json::Value& v = target.append(Json::objectValue);
+        DatasetToJson(v, *child, format, flags, maxStringLength, encoding);
+      }
+    }
+  }
+
+
+  void FromDcmtkBridge::DatasetToJson(Json::Value& parent,
+                                      DcmItem& item,
+                                      DicomToJsonFormat format,
+                                      DicomToJsonFlags flags,
+                                      unsigned int maxStringLength,
+                                      Encoding encoding)
+  {
+    assert(parent.type() == Json::objectValue);
+
+    for (unsigned long i = 0; i < item.card(); i++)
+    {
+      DcmElement* element = item.getElement(i);
+      if (element == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
+
+      /*element->getTag().isPrivate()*/
+      if (tag.IsPrivate() &&
+          !(flags & DicomToJsonFlags_IncludePrivateTags))    
+      {
+        continue;
+      }
+
+      if (!(flags & DicomToJsonFlags_IncludeUnknownTags))
+      {
+        DictionaryLocker locker;
+        if (locker->findEntry(element->getTag(), NULL) == NULL)
+        {
+          continue;
+        }
+      }
+
+      DcmEVR evr = element->getTag().getEVR();
+      if (evr == EVR_OB ||
+          evr == EVR_OF ||
+          evr == EVR_OW ||
+          evr == EVR_UN ||
+          evr == EVR_ox)
+      {
+        // This is a binary tag
+        if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) ||
+            (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary)))
+        {
+          continue;
+        }
+      }
+
+      FromDcmtkBridge::ElementToJson(parent, *element, format, flags, maxStringLength, encoding);
+    }
+  }
+
+
+  void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, 
+                                           DcmDataset& dataset,
+                                           DicomToJsonFormat format,
+                                           DicomToJsonFlags flags,
+                                           unsigned int maxStringLength,
+                                           Encoding defaultEncoding)
+  {
+    Encoding encoding = DetectEncoding(dataset, defaultEncoding);
+
+    target = Json::objectValue;
+    DatasetToJson(target, dataset, format, flags, maxStringLength, encoding);
+  }
+
+
+  void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target, 
+                                            DcmMetaInfo& dataset,
+                                            DicomToJsonFormat format,
+                                            DicomToJsonFlags flags,
+                                            unsigned int maxStringLength)
+  {
+    target = Json::objectValue;
+    DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii);
+  }
+
+
+
+  static std::string GetTagNameInternal(DcmTag& tag)
+  {
+    {
+      // Some patches for important tags because of different DICOM
+      // dictionaries between DCMTK versions
+      DicomTag tmp(tag.getGroup(), tag.getElement());
+      std::string n = tmp.GetMainTagsName();
+      if (n.size() != 0)
+      {
+        return n;
+      }
+      // End of patches
+    }
+
+#if 0
+    // This version explicitly calls the dictionary
+    const DcmDataDictionary& dict = dcmDataDict.rdlock();
+    const DcmDictEntry* entry = dict.findEntry(tag, NULL);
+
+    std::string s(DcmTag_ERROR_TagName);
+    if (entry != NULL)
+    {
+      s = std::string(entry->getTagName());
+    }
+
+    dcmDataDict.unlock();
+    return s;
+#else
+    const char* name = tag.getTagName();
+    if (name == NULL)
+    {
+      return DcmTag_ERROR_TagName;
+    }
+    else
+    {
+      return std::string(name);
+    }
+#endif
+  }
+
+
+  std::string FromDcmtkBridge::GetTagName(const DicomTag& t,
+                                          const std::string& privateCreator)
+  {
+    DcmTag tag(t.GetGroup(), t.GetElement());
+
+    if (!privateCreator.empty())
+    {
+      tag.setPrivateCreator(privateCreator.c_str());
+    }
+
+    return GetTagNameInternal(tag);
+  }
+
+
+  std::string FromDcmtkBridge::GetTagName(const DcmElement& element)
+  {
+    // Copy the tag to ensure const-correctness of DcmElement. Note
+    // that the private creator information is also copied.
+    DcmTag tag(element.getTag());  
+
+    return GetTagNameInternal(tag);
+  }
+
+
+
+  DicomTag FromDcmtkBridge::ParseTag(const char* name)
+  {
+    if (strlen(name) == 9 &&
+        isxdigit(name[0]) &&
+        isxdigit(name[1]) &&
+        isxdigit(name[2]) &&
+        isxdigit(name[3]) &&
+        (name[4] == '-' || name[4] == ',') &&
+        isxdigit(name[5]) &&
+        isxdigit(name[6]) &&
+        isxdigit(name[7]) &&
+        isxdigit(name[8]))        
+    {
+      uint16_t group = GetTagValue(name);
+      uint16_t element = GetTagValue(name + 5);
+      return DicomTag(group, element);
+    }
+
+    if (strlen(name) == 8 &&
+        isxdigit(name[0]) &&
+        isxdigit(name[1]) &&
+        isxdigit(name[2]) &&
+        isxdigit(name[3]) &&
+        isxdigit(name[4]) &&
+        isxdigit(name[5]) &&
+        isxdigit(name[6]) &&
+        isxdigit(name[7]))        
+    {
+      uint16_t group = GetTagValue(name);
+      uint16_t element = GetTagValue(name + 4);
+      return DicomTag(group, element);
+    }
+
+#if 0
+    const DcmDataDictionary& dict = dcmDataDict.rdlock();
+    const DcmDictEntry* entry = dict.findEntry(name);
+
+    if (entry == NULL)
+    {
+      dcmDataDict.unlock();
+      throw OrthancException(ErrorCode_UnknownDicomTag);
+    }
+    else
+    {
+      DcmTagKey key = entry->getKey();
+      DicomTag tag(key.getGroup(), key.getElement());
+      dcmDataDict.unlock();
+      return tag;
+    }
+#else
+    DcmTag tag;
+    if (DcmTag::findTagFromName(name, tag).good())
+    {
+      return DicomTag(tag.getGTag(), tag.getETag());
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_UnknownDicomTag);
+    }
+#endif
+  }
+
+
+  bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag)
+  {
+    DcmTag tmp(tag.GetGroup(), tag.GetElement());
+    return tmp.isUnknownVR();
+  }
+
+
+  void FromDcmtkBridge::ToJson(Json::Value& result,
+                               const DicomMap& values,
+                               bool simplify)
+  {
+    if (result.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    result.clear();
+
+    for (DicomMap::Map::const_iterator 
+           it = values.map_.begin(); it != values.map_.end(); ++it)
+    {
+      // TODO Inject PrivateCreator if some is available in the DicomMap?
+      const std::string tagName = GetTagName(it->first, "");
+
+      if (simplify)
+      {
+        if (it->second->IsNull())
+        {
+          result[tagName] = Json::nullValue;
+        }
+        else
+        {
+          // TODO IsBinary
+          result[tagName] = it->second->GetContent();
+        }
+      }
+      else
+      {
+        Json::Value value = Json::objectValue;
+
+        value["Name"] = tagName;
+
+        if (it->second->IsNull())
+        {
+          value["Type"] = "Null";
+          value["Value"] = Json::nullValue;
+        }
+        else
+        {
+          // TODO IsBinary
+          value["Type"] = "String";
+          value["Value"] = it->second->GetContent();
+        }
+
+        result[it->first.Format()] = value;
+      }
+    }
+  }
+
+
+  std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level)
+  {
+    char uid[100];
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        // The "PatientID" field is of type LO (Long String), 64
+        // Bytes Maximum. An UUID is of length 36, thus it can be used
+        // as a random PatientID.
+        return SystemToolbox::GenerateUuid();
+
+      case ResourceType_Instance:
+        return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
+
+      case ResourceType_Series:
+        return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT);
+
+      case ResourceType_Study:
+        return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT);
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
+                                           DcmDataset& dataSet)
+  {
+    // Determine the transfer syntax which shall be used to write the
+    // information to the file. We always switch to the Little Endian
+    // syntax, with explicit length.
+
+    // http://support.dcmtk.org/docs/dcxfer_8h-source.html
+
+
+    /**
+     * Note that up to Orthanc 0.7.1 (inclusive), the
+     * "EXS_LittleEndianExplicit" was always used to save the DICOM
+     * dataset into memory. We now keep the original transfer syntax
+     * (if available).
+     **/
+    E_TransferSyntax xfer = dataSet.getOriginalXfer();
+    if (xfer == EXS_Unknown)
+    {
+      // No information about the original transfer syntax: This is
+      // most probably a DICOM dataset that was read from memory.
+      xfer = EXS_LittleEndianExplicit;
+    }
+
+    E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
+
+    // Create the meta-header information
+    DcmFileFormat ff(&dataSet);
+    ff.validateMetaInfo(xfer);
+    ff.removeInvalidGroups();
+
+    // Create a memory buffer with the proper size
+    {
+      const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType);  // (*)
+      buffer.resize(estimatedSize);
+    }
+
+    DcmOutputBufferStream ob(&buffer[0], buffer.size());
+
+    // Fill the memory buffer with the meta-header and the dataset
+    ff.transferInit();
+    OFCondition c = ff.write(ob, xfer, encodingType, NULL,
+                             /*opt_groupLength*/ EGL_recalcGL,
+                             /*opt_paddingType*/ EPD_withoutPadding);
+    ff.transferEnd();
+
+    if (c.good())
+    {
+      // The DICOM file is successfully written, truncate the target
+      // buffer if its size was overestimated by (*)
+      ob.flush();
+
+      size_t effectiveSize = static_cast<size_t>(ob.tell());
+      if (effectiveSize < buffer.size())
+      {
+        buffer.resize(effectiveSize);
+      }
+
+      return true;
+    }
+    else
+    {
+      // Error
+      buffer.clear();
+      return false;
+    }
+  }
+
+
+  ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag)
+  {
+    DcmTag t(tag.GetGroup(), tag.GetElement());
+    return Convert(t.getEVR());
+  }
+
+  ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr)
+  {
+    switch (vr)
+    {
+      case EVR_AE:
+        return ValueRepresentation_ApplicationEntity;
+
+      case EVR_AS:
+        return ValueRepresentation_AgeString;
+
+      case EVR_AT:
+        return ValueRepresentation_AttributeTag;
+
+      case EVR_CS:
+        return ValueRepresentation_CodeString;
+
+      case EVR_DA:
+        return ValueRepresentation_Date;
+
+      case EVR_DS:
+        return ValueRepresentation_DecimalString;
+
+      case EVR_DT:
+        return ValueRepresentation_DateTime;
+
+      case EVR_FL:
+        return ValueRepresentation_FloatingPointSingle;
+
+      case EVR_FD:
+        return ValueRepresentation_FloatingPointDouble;
+
+      case EVR_IS:
+        return ValueRepresentation_IntegerString;
+
+      case EVR_LO:
+        return ValueRepresentation_LongString;
+
+      case EVR_LT:
+        return ValueRepresentation_LongText;
+
+      case EVR_OB:
+        return ValueRepresentation_OtherByte;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case EVR_OD:
+          return ValueRepresentation_OtherDouble;*/
+
+      case EVR_OF:
+        return ValueRepresentation_OtherFloat;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case EVR_OL:
+          return ValueRepresentation_OtherLong;*/
+
+      case EVR_OW:
+        return ValueRepresentation_OtherWord;
+
+      case EVR_PN:
+        return ValueRepresentation_PersonName;
+
+      case EVR_SH:
+        return ValueRepresentation_ShortString;
+
+      case EVR_SL:
+        return ValueRepresentation_SignedLong;
+
+      case EVR_SQ:
+        return ValueRepresentation_Sequence;
+
+      case EVR_SS:
+        return ValueRepresentation_SignedShort;
+
+      case EVR_ST:
+        return ValueRepresentation_ShortText;
+
+      case EVR_TM:
+        return ValueRepresentation_Time;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case EVR_UC:
+          return ValueRepresentation_UnlimitedCharacters;*/
+
+      case EVR_UI:
+        return ValueRepresentation_UniqueIdentifier;
+
+      case EVR_UL:
+        return ValueRepresentation_UnsignedLong;
+
+      case EVR_UN:
+        return ValueRepresentation_Unknown;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case EVR_UR:
+          return ValueRepresentation_UniversalResource;*/
+
+      case EVR_US:
+        return ValueRepresentation_UnsignedShort;
+
+      case EVR_UT:
+        return ValueRepresentation_UnlimitedText;
+
+      default:
+        return ValueRepresentation_NotSupported;
+    }
+  }
+
+
+  static bool IsBinaryTag(const DcmTag& key)
+  {
+    return (key.isUnknownVR() || 
+            key.getEVR() == EVR_OB ||
+            key.getEVR() == EVR_OF ||
+            key.getEVR() == EVR_OW ||
+            key.getEVR() == EVR_UN ||
+            key.getEVR() == EVR_ox);
+  }
+
+
+  DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag)
+  {
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+
+    if (tag.IsPrivate() ||
+        IsBinaryTag(key))
+    {
+      return new DcmOtherByteOtherWord(key);
+    }
+
+    switch (key.getEVR())
+    {
+      // http://support.dcmtk.org/docs/dcvr_8h-source.html
+
+      /**
+       * Binary types, handled above
+       **/
+    
+      case EVR_OB:  // other byte
+      case EVR_OF:  // other float
+      case EVR_OW:  // other word
+      case EVR_UN:  // unknown value representation
+      case EVR_ox:  // OB or OW depending on context
+        throw OrthancException(ErrorCode_InternalError);
+
+
+      /**
+       * String types.
+       * http://support.dcmtk.org/docs/classDcmByteString.html
+       **/
+      
+      case EVR_AS:  // age string
+        return new DcmAgeString(key);
+
+      case EVR_AE:  // application entity title
+        return new DcmApplicationEntity(key);
+
+      case EVR_CS:  // code string
+        return new DcmCodeString(key);        
+
+      case EVR_DA:  // date string
+        return new DcmDate(key);
+        
+      case EVR_DT:  // date time string
+        return new DcmDateTime(key);
+
+      case EVR_DS:  // decimal string
+        return new DcmDecimalString(key);
+
+      case EVR_IS:  // integer string
+        return new DcmIntegerString(key);
+
+      case EVR_TM:  // time string
+        return new DcmTime(key);
+
+      case EVR_UI:  // unique identifier
+        return new DcmUniqueIdentifier(key);
+
+      case EVR_ST:  // short text
+        return new DcmShortText(key);
+
+      case EVR_LO:  // long string
+        return new DcmLongString(key);
+
+      case EVR_LT:  // long text
+        return new DcmLongText(key);
+
+      case EVR_UT:  // unlimited text
+        return new DcmUnlimitedText(key);
+
+      case EVR_SH:  // short string
+        return new DcmShortString(key);
+
+      case EVR_PN:  // person name
+        return new DcmPersonName(key);
+
+        
+      /**
+       * Numerical types
+       **/ 
+      
+      case EVR_SL:  // signed long
+        return new DcmSignedLong(key);
+
+      case EVR_SS:  // signed short
+        return new DcmSignedShort(key);
+
+      case EVR_UL:  // unsigned long
+        return new DcmUnsignedLong(key);
+
+      case EVR_US:  // unsigned short
+        return new DcmUnsignedShort(key);
+
+      case EVR_FL:  // float single-precision
+        return new DcmFloatingPointSingle(key);
+
+      case EVR_FD:  // float double-precision
+        return new DcmFloatingPointDouble(key);
+
+
+      /**
+       * Sequence types, should never occur at this point.
+       **/
+
+      case EVR_SQ:  // sequence of items
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+
+
+      /**
+       * TODO
+       **/
+
+      case EVR_AT:  // attribute tag
+        throw OrthancException(ErrorCode_NotImplemented);
+
+
+      /**
+       * Internal to DCMTK.
+       **/ 
+
+      case EVR_xs:  // SS or US depending on context
+      case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
+      case EVR_na:  // na="not applicable", for data which has no VR
+      case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
+      case EVR_item:  // used internally for items
+      case EVR_metainfo:  // used internally for meta info datasets
+      case EVR_dataset:  // used internally for datasets
+      case EVR_fileFormat:  // used internally for DICOM files
+      case EVR_dicomDir:  // used internally for DICOMDIR objects
+      case EVR_dirRecord:  // used internally for DICOMDIR records
+      case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
+      case EVR_pixelItem:  // used internally for pixel items in a compressed image
+      case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
+      case EVR_PixelData:  // used internally for uncompressed pixeld data
+      case EVR_OverlayData:  // used internally for overlay data
+      case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
+      default:
+        break;
+    }
+
+    throw OrthancException(ErrorCode_InternalError);          
+  }
+
+
+
+  void FromDcmtkBridge::FillElementWithString(DcmElement& element,
+                                              const DicomTag& tag,
+                                              const std::string& utf8Value,
+                                              bool decodeDataUriScheme,
+                                              Encoding dicomEncoding)
+  {
+    std::string binary;
+    const std::string* decoded = &utf8Value;
+
+    if (decodeDataUriScheme &&
+        boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
+    {
+      std::string mime;
+      if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value))
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      decoded = &binary;
+    }
+    else if (dicomEncoding != Encoding_Utf8)
+    {
+      binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding);
+      decoded = &binary;
+    }
+
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+
+    if (tag.IsPrivate() ||
+        IsBinaryTag(key))
+    {
+      if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good())
+      {
+        return;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    bool ok = false;
+    
+    try
+    {
+      switch (key.getEVR())
+      {
+        // http://support.dcmtk.org/docs/dcvr_8h-source.html
+
+        /**
+         * TODO.
+         **/
+
+        case EVR_OB:  // other byte
+        case EVR_OF:  // other float
+        case EVR_OW:  // other word
+        case EVR_AT:  // attribute tag
+          throw OrthancException(ErrorCode_NotImplemented);
+    
+        case EVR_UN:  // unknown value representation
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+
+
+        /**
+         * String types.
+         **/
+      
+        case EVR_DS:  // decimal string
+        case EVR_IS:  // integer string
+        case EVR_AS:  // age string
+        case EVR_DA:  // date string
+        case EVR_DT:  // date time string
+        case EVR_TM:  // time string
+        case EVR_AE:  // application entity title
+        case EVR_CS:  // code string
+        case EVR_SH:  // short string
+        case EVR_LO:  // long string
+        case EVR_ST:  // short text
+        case EVR_LT:  // long text
+        case EVR_UT:  // unlimited text
+        case EVR_PN:  // person name
+        case EVR_UI:  // unique identifier
+        {
+          ok = element.putString(decoded->c_str()).good();
+          break;
+        }
+
+        
+        /**
+         * Numerical types
+         **/ 
+      
+        case EVR_SL:  // signed long
+        {
+          ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good();
+          break;
+        }
+
+        case EVR_SS:  // signed short
+        {
+          ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
+          break;
+        }
+
+        case EVR_UL:  // unsigned long
+        {
+          ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good();
+          break;
+        }
+
+        case EVR_US:  // unsigned short
+        {
+          ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();
+          break;
+        }
+
+        case EVR_FL:  // float single-precision
+        {
+          ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good();
+          break;
+        }
+
+        case EVR_FD:  // float double-precision
+        {
+          ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good();
+          break;
+        }
+
+
+        /**
+         * Sequence types, should never occur at this point.
+         **/
+
+        case EVR_SQ:  // sequence of items
+        {
+          ok = false;
+          break;
+        }
+
+
+        /**
+         * Internal to DCMTK.
+         **/ 
+
+        case EVR_ox:  // OB or OW depending on context
+        case EVR_xs:  // SS or US depending on context
+        case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
+        case EVR_na:  // na="not applicable", for data which has no VR
+        case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
+        case EVR_item:  // used internally for items
+        case EVR_metainfo:  // used internally for meta info datasets
+        case EVR_dataset:  // used internally for datasets
+        case EVR_fileFormat:  // used internally for DICOM files
+        case EVR_dicomDir:  // used internally for DICOMDIR objects
+        case EVR_dirRecord:  // used internally for DICOMDIR records
+        case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
+        case EVR_pixelItem:  // used internally for pixel items in a compressed image
+        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
+        case EVR_PixelData:  // used internally for uncompressed pixeld data
+        case EVR_OverlayData:  // used internally for overlay data
+        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
+        default:
+          break;
+      }
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      ok = false;
+    }
+
+    if (!ok)
+    {
+      LOG(ERROR) << "While creating a DICOM instance, tag (" << tag.Format()
+                 << ") has out-of-range value: \"" << *decoded << "\"";
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag,
+                                        const Json::Value& value,
+                                        bool decodeDataUriScheme,
+                                        Encoding dicomEncoding)
+  {
+    std::auto_ptr<DcmElement> element;
+
+    switch (value.type())
+    {
+      case Json::stringValue:
+        element.reset(CreateElementForTag(tag));
+        FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding);
+        break;
+
+      case Json::nullValue:
+        element.reset(CreateElementForTag(tag));
+        FillElementWithString(*element, tag, "", decodeDataUriScheme, dicomEncoding);
+        break;
+
+      case Json::arrayValue:
+      {
+        DcmTag key(tag.GetGroup(), tag.GetElement());
+        if (key.getEVR() != EVR_SQ)
+        {
+          throw OrthancException(ErrorCode_BadParameterType);
+        }
+
+        DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key);
+        element.reset(sequence);
+        
+        for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
+        {
+          std::auto_ptr<DcmItem> item(new DcmItem);
+
+          Json::Value::Members members = value[i].getMemberNames();
+          for (Json::Value::ArrayIndex j = 0; j < members.size(); j++)
+          {
+            item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding));
+          }
+
+          sequence->append(item.release());
+        }
+
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    return element.release();
+  }
+
+
+  DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset)
+  {
+    DcmElement *element = NULL;
+    if (!dataset.findAndGetElement(DCM_PixelData, element).good())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
+    DcmPixelSequence* pixelSequence = NULL;
+    if (!pixelData.getEncapsulatedRepresentation
+        (dataset.getOriginalXfer(), NULL, pixelSequence).good())
+    {
+      return NULL;
+    }
+    else
+    {
+      return pixelSequence;
+    }
+  }
+
+
+  Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json,
+                                            Encoding defaultEncoding)
+  {
+    if (json.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    Encoding encoding = defaultEncoding;
+
+    const Json::Value::Members tags = json.getMemberNames();
+    
+    // Look for SpecificCharacterSet (0008,0005) in the JSON file
+    for (size_t i = 0; i < tags.size(); i++)
+    {
+      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
+      if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        const Json::Value& value = json[tags[i]];
+        if (value.type() != Json::stringValue ||
+            (value.asString().length() != 0 &&
+             !GetDicomEncoding(encoding, value.asCString())))
+        {
+          LOG(ERROR) << "Unknown encoding while creating DICOM from JSON: " << value;
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+
+        if (value.asString().length() == 0)
+        {
+          return defaultEncoding;
+        }
+      }
+    }
+
+    return encoding;
+  } 
+
+
+  static void SetString(DcmDataset& target,
+                        const DcmTag& tag,
+                        const std::string& value)
+  {
+    if (!target.putAndInsertString(tag, value.c_str()).good())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json,  // Encoded using UTF-8
+                                        bool generateIdentifiers,
+                                        bool decodeDataUriScheme,
+                                        Encoding defaultEncoding)
+  {
+    std::auto_ptr<DcmDataset> result(new DcmDataset);
+    Encoding encoding = ExtractEncoding(json, defaultEncoding);
+
+    SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding));
+
+    const Json::Value::Members tags = json.getMemberNames();
+    
+    bool hasPatientId = false;
+    bool hasStudyInstanceUid = false;
+    bool hasSeriesInstanceUid = false;
+    bool hasSopInstanceUid = false;
+
+    for (size_t i = 0; i < tags.size(); i++)
+    {
+      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
+      const Json::Value& value = json[tags[i]];
+
+      if (tag == DICOM_TAG_PATIENT_ID)
+      {
+        hasPatientId = true;
+      }
+      else if (tag == DICOM_TAG_STUDY_INSTANCE_UID)
+      {
+        hasStudyInstanceUid = true;
+      }
+      else if (tag == DICOM_TAG_SERIES_INSTANCE_UID)
+      {
+        hasSeriesInstanceUid = true;
+      }
+      else if (tag == DICOM_TAG_SOP_INSTANCE_UID)
+      {
+        hasSopInstanceUid = true;
+      }
+
+      if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding));
+        const DcmTagKey& tag = element->getTag();
+
+        result->findAndDeleteElement(tag);
+
+        DcmElement* tmp = element.release();
+        if (!result->insert(tmp, false, false).good())
+        {
+          delete tmp;
+          throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+    }
+
+    if (!hasPatientId &&
+        generateIdentifiers)
+    {
+      SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient));
+    }
+
+    if (!hasStudyInstanceUid &&
+        generateIdentifiers)
+    {
+      SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study));
+    }
+
+    if (!hasSeriesInstanceUid &&
+        generateIdentifiers)
+    {
+      SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series));
+    }
+
+    if (!hasSopInstanceUid &&
+        generateIdentifiers)
+    {
+      SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance));
+    }
+
+    return result.release();
+  }
+
+
+  DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer,
+                                                       size_t size)
+  {
+    DcmInputBufferStream is;
+    if (size > 0)
+    {
+      is.setBuffer(buffer, size);
+    }
+    is.setEos();
+
+    std::auto_ptr<DcmFileFormat> result(new DcmFileFormat);
+
+    result->transferInit();
+    if (!result->read(is).good())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    result->loadAllDataIntoMemory();
+    result->transferEnd();
+
+    return result.release();
+  }
+
+
+  void FromDcmtkBridge::FromJson(DicomMap& target,
+                                 const Json::Value& source)
+  {
+    if (source.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    target.Clear();
+
+    Json::Value::Members members = source.getMemberNames();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& value = source[members[i]];
+
+      if (value.type() != Json::stringValue)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+      
+      target.SetValue(ParseTag(members[i]), value.asString(), false);
+    }
+  }
+
+
+  void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset,
+                                             Encoding source,
+                                             Encoding target)
+  {
+    // Recursive exploration of a dataset to change the encoding of
+    // each string-like element
+
+    if (source == target)
+    {
+      return;
+    }
+
+    for (unsigned long i = 0; i < dataset.card(); i++)
+    {
+      DcmElement* element = dataset.getElement(i);
+      if (element)
+      {
+        if (element->isLeaf())
+        {
+          char *c = NULL;
+          if (element->isaString() &&
+              element->getString(c).good() && 
+              c != NULL)
+          {
+            std::string a = Toolbox::ConvertToUtf8(c, source);
+            std::string b = Toolbox::ConvertFromUtf8(a, target);
+            element->putString(b.c_str());
+          }
+        }
+        else
+        {
+          // "All subclasses of DcmElement except for DcmSequenceOfItems
+          // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
+          // etc. are not." The following dynamic_cast is thus OK.
+          DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
+
+          for (unsigned long j = 0; j < sequence.card(); j++)
+          {
+            ChangeStringEncoding(*sequence.getItem(j), source, target);
+          }
+        }
+      }
+    }
+  }
+
+
+  bool FromDcmtkBridge::LookupTransferSyntax(std::string& result,
+                                             DcmFileFormat& dicom)
+  {
+    const char* value = NULL;
+
+    if (dicom.getMetaInfo() != NULL &&
+        dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() &&
+        value != NULL)
+    {
+      result.assign(value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+#if ORTHANC_ENABLE_LUA == 1
+  void FromDcmtkBridge::ExecuteToDicom(DicomMap& target,
+                                       LuaFunctionCall& call)
+  {
+    Json::Value output;
+    call.ExecuteToJson(output, true /* keep strings */);
+
+    target.Clear();
+
+    if (output.type() == Json::arrayValue &&
+        output.size() == 0)
+    {
+      // This case happens for empty tables
+      return;
+    }
+
+    if (output.type() != Json::objectValue)
+    {
+      LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table";
+      throw OrthancException(ErrorCode_LuaBadOutput);
+    }
+
+    Json::Value::Members members = output.getMemberNames();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      if (output[members[i]].type() != Json::stringValue)
+      {
+        LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table mapping names of DICOM tags to strings";
+        throw OrthancException(ErrorCode_LuaBadOutput);
+      }
+
+      DicomTag tag(ParseTag(members[i]));
+      target.SetValue(tag, output[members[i]].asString(), false);
+    }
+  }
+#endif
+
+
+  void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, 
+                                            DcmItem& dataset)
+  {
+    ExtractDicomSummary(target, dataset,
+                        ORTHANC_MAXIMUM_TAG_LENGTH,
+                        GetDefaultDicomEncoding());
+  }
+
+  
+  void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, 
+                                           DcmDataset& dataset)
+  {
+    ExtractDicomAsJson(target, dataset, 
+                       DicomToJsonFormat_Full,
+                       DicomToJsonFlags_Default, 
+                       ORTHANC_MAXIMUM_TAG_LENGTH,
+                       GetDefaultDicomEncoding());
+  }
+
+
+  void FromDcmtkBridge::InitializeCodecs()
+  {
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+    LOG(WARNING) << "Registering JPEG Lossless codecs in DCMTK";
+    DJLSDecoderRegistration::registerCodecs();    
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    LOG(WARNING) << "Registering JPEG codecs in DCMTK";
+    DJDecoderRegistration::registerCodecs(); 
+#endif
+  }
+
+
+  void FromDcmtkBridge::FinalizeCodecs()
+  {
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+    // Unregister JPEG-LS codecs
+    DJLSDecoderRegistration::cleanup();
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    // Unregister JPEG codecs
+    DJDecoderRegistration::cleanup();
+#endif
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/FromDcmtkBridge.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,239 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../DicomFormat/DicomElement.h"
+#include "../DicomFormat/DicomMap.h"
+
+#include <dcmtk/dcmdata/dcdatset.h>
+#include <dcmtk/dcmdata/dcmetinf.h>
+#include <dcmtk/dcmdata/dcpixseq.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <json/json.h>
+
+#if !defined(ORTHANC_ENABLE_LUA)
+#  error The macro ORTHANC_ENABLE_LUA must be defined
+#endif
+
+#if ORTHANC_ENABLE_DCMTK != 1
+#  error The macro ORTHANC_ENABLE_DCMTK must be set to 1
+#endif
+
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+#  include <gtest/gtest_prod.h>
+#endif
+
+#if ORTHANC_ENABLE_LUA == 1
+#  include "../Lua/LuaFunctionCall.h"
+#endif
+
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG)
+#  error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS)
+#  error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined
+#endif
+
+
+namespace Orthanc
+{
+  class FromDcmtkBridge : public boost::noncopyable
+  {
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+    FRIEND_TEST(FromDcmtkBridge, FromJson);
+#endif
+
+    friend class ParsedDicomFile;
+
+  private:
+    FromDcmtkBridge();  // Pure static class
+
+    static void ExtractDicomSummary(DicomMap& target, 
+                                    DcmItem& dataset,
+                                    unsigned int maxStringLength,
+                                    Encoding defaultEncoding);
+
+    static void DatasetToJson(Json::Value& parent,
+                              DcmItem& item,
+                              DicomToJsonFormat format,
+                              DicomToJsonFlags flags,
+                              unsigned int maxStringLength,
+                              Encoding encoding);
+
+    static void ElementToJson(Json::Value& parent,
+                              DcmElement& element,
+                              DicomToJsonFormat format,
+                              DicomToJsonFlags flags,
+                              unsigned int maxStringLength,
+                              Encoding dicomEncoding);
+
+    static void ExtractDicomAsJson(Json::Value& target, 
+                                   DcmDataset& dataset,
+                                   DicomToJsonFormat format,
+                                   DicomToJsonFlags flags,
+                                   unsigned int maxStringLength,
+                                   Encoding defaultEncoding);
+
+    static void ChangeStringEncoding(DcmItem& dataset,
+                                     Encoding source,
+                                     Encoding target);
+
+  public:
+    static void InitializeDictionary(bool loadPrivateDictionary);
+
+    static void RegisterDictionaryTag(const DicomTag& tag,
+                                      ValueRepresentation vr,
+                                      const std::string& name,
+                                      unsigned int minMultiplicity,
+                                      unsigned int maxMultiplicity,
+                                      const std::string& privateCreator);
+
+    static Encoding DetectEncoding(DcmItem& dataset,
+                                   Encoding defaultEncoding);
+
+    static DicomTag Convert(const DcmTag& tag);
+
+    static DicomTag GetTag(const DcmElement& element);
+
+    static bool IsUnknownTag(const DicomTag& tag);
+
+    static DicomValue* ConvertLeafElement(DcmElement& element,
+                                          DicomToJsonFlags flags,
+                                          unsigned int maxStringLength,
+                                          Encoding encoding);
+
+    static void ExtractHeaderAsJson(Json::Value& target, 
+                                    DcmMetaInfo& header,
+                                    DicomToJsonFormat format,
+                                    DicomToJsonFlags flags,
+                                    unsigned int maxStringLength);
+
+    static std::string GetTagName(const DicomTag& tag,
+                                  const std::string& privateCreator);
+
+    static std::string GetTagName(const DcmElement& element);
+
+    static std::string GetTagName(const DicomElement& element)
+    {
+      return GetTagName(element.GetTag(), "");
+    }
+
+    static DicomTag ParseTag(const char* name);
+
+    static DicomTag ParseTag(const std::string& name)
+    {
+      return ParseTag(name.c_str());
+    }
+
+    static bool HasTag(const DicomMap& fields,
+                       const std::string& tagName)
+    {
+      return fields.HasTag(ParseTag(tagName));
+    }
+
+    static const DicomValue& GetValue(const DicomMap& fields,
+                                      const std::string& tagName)
+    {
+      return fields.GetValue(ParseTag(tagName));
+    }
+
+    static void SetValue(DicomMap& target,
+                         const std::string& tagName,
+                         DicomValue* value)
+    {
+      target.SetValue(ParseTag(tagName), value);
+    }
+
+    static void ToJson(Json::Value& result,
+                       const DicomMap& values,
+                       bool simplify);
+
+    static std::string GenerateUniqueIdentifier(ResourceType level);
+
+    static bool SaveToMemoryBuffer(std::string& buffer,
+                                   DcmDataset& dataSet);
+
+    static ValueRepresentation Convert(DcmEVR vr);
+
+    static ValueRepresentation LookupValueRepresentation(const DicomTag& tag);
+
+    static DcmElement* CreateElementForTag(const DicomTag& tag);
+    
+    static void FillElementWithString(DcmElement& element,
+                                      const DicomTag& tag,
+                                      const std::string& utf8alue,  // Encoded using UTF-8
+                                      bool decodeDataUriScheme,
+                                      Encoding dicomEncoding);
+
+    static DcmElement* FromJson(const DicomTag& tag,
+                                const Json::Value& element,  // Encoded using UTF-8
+                                bool decodeDataUriScheme,
+                                Encoding dicomEncoding);
+
+    static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset);
+
+    static Encoding ExtractEncoding(const Json::Value& json,
+                                    Encoding defaultEncoding);
+
+    static DcmDataset* FromJson(const Json::Value& json,  // Encoded using UTF-8
+                                bool generateIdentifiers,
+                                bool decodeDataUriScheme,
+                                Encoding defaultEncoding);
+
+    static DcmFileFormat* LoadFromMemoryBuffer(const void* buffer,
+                                               size_t size);
+
+    static void FromJson(DicomMap& values,
+                         const Json::Value& result);
+
+    static bool LookupTransferSyntax(std::string& result,
+                                     DcmFileFormat& dicom);
+
+#if ORTHANC_ENABLE_LUA == 1
+    static void ExecuteToDicom(DicomMap& target,
+                               LuaFunctionCall& call);
+#endif
+
+    static void ExtractDicomSummary(DicomMap& target, 
+                                    DcmItem& dataset);
+
+    static void ExtractDicomAsJson(Json::Value& target, 
+                                   DcmDataset& dataset);
+
+    static void InitializeCodecs();
+
+    static void FinalizeCodecs();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/Internals/DicomFrameIndex.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,439 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../../PrecompiledHeaders.h"
+#include "DicomFrameIndex.h"
+
+#include "../../OrthancException.h"
+#include "../../DicomFormat/DicomImageInformation.h"
+#include "../FromDcmtkBridge.h"
+#include "../../Endianness.h"
+#include "DicomImageDecoder.h"
+
+#include <boost/lexical_cast.hpp>
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcpxitem.h>
+#include <dcmtk/dcmdata/dcpixseq.h>
+
+namespace Orthanc
+{
+  class DicomFrameIndex::FragmentIndex : public DicomFrameIndex::IIndex
+  {
+  private:
+    DcmPixelSequence*           pixelSequence_;
+    std::vector<DcmPixelItem*>  startFragment_;
+    std::vector<unsigned int>   countFragments_;
+    std::vector<unsigned int>   frameSize_;
+
+    void GetOffsetTable(std::vector<uint32_t>& table)
+    {
+      DcmPixelItem* item = NULL;
+      if (!pixelSequence_->getItem(item, 0).good() ||
+          item == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      uint32_t length = item->getLength();
+      if (length == 0)
+      {
+        table.clear();
+        return;
+      }
+
+      if (length % 4 != 0)
+      {
+        // Error: Each fragment is index with 4 bytes (uint32_t)
+        throw OrthancException(ErrorCode_BadFileFormat);        
+      }
+
+      uint8_t* content = NULL;
+      if (!item->getUint8Array(content).good() ||
+          content == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      table.resize(length / 4);
+
+      // The offset table is always in little endian in the DICOM
+      // file. Swap it to host endianness if needed.
+      const uint32_t* offset = reinterpret_cast<const uint32_t*>(content);
+      for (size_t i = 0; i < table.size(); i++, offset++)
+      {
+        table[i] = le32toh(*offset);
+      }
+    }
+
+
+  public:
+    FragmentIndex(DcmPixelSequence* pixelSequence,
+                  unsigned int countFrames) :
+      pixelSequence_(pixelSequence)
+    {
+      assert(pixelSequence != NULL);
+
+      startFragment_.resize(countFrames);
+      countFragments_.resize(countFrames);
+      frameSize_.resize(countFrames);
+
+      // The first fragment corresponds to the offset table
+      unsigned int countFragments = static_cast<unsigned int>(pixelSequence_->card());
+      if (countFragments < countFrames + 1)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      if (countFragments == countFrames + 1)
+      {
+        // Simple case: There is one fragment per frame.
+
+        DcmObject* fragment = pixelSequence_->nextInContainer(NULL);  // Skip the offset table
+        if (fragment == NULL)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        for (unsigned int i = 0; i < countFrames; i++)
+        {
+          fragment = pixelSequence_->nextInContainer(fragment);
+          startFragment_[i] = dynamic_cast<DcmPixelItem*>(fragment);
+          frameSize_[i] = fragment->getLength();
+          countFragments_[i] = 1;
+        }
+
+        return;
+      }
+
+      // Parse the offset table
+      std::vector<uint32_t> offsetOfFrame;
+      GetOffsetTable(offsetOfFrame);
+      
+      if (offsetOfFrame.size() != countFrames ||
+          offsetOfFrame[0] != 0)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+
+      // Loop over the fragments (ignoring the offset table). This is
+      // an alternative, faster implementation to DCMTK's
+      // "DcmCodec::determineStartFragment()".
+      DcmObject* fragment = pixelSequence_->nextInContainer(NULL);
+      if (fragment == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      fragment = pixelSequence_->nextInContainer(fragment); // Skip the offset table
+      if (fragment == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      uint32_t offset = 0;
+      unsigned int currentFrame = 0;
+      startFragment_[0] = dynamic_cast<DcmPixelItem*>(fragment);
+
+      unsigned int currentFragment = 1;
+      while (fragment != NULL)
+      {
+        if (currentFrame + 1 < countFrames &&
+            offset == offsetOfFrame[currentFrame + 1])
+        {
+          currentFrame += 1;
+          startFragment_[currentFrame] = dynamic_cast<DcmPixelItem*>(fragment);
+        }
+
+        frameSize_[currentFrame] += fragment->getLength();
+        countFragments_[currentFrame]++;
+
+        // 8 bytes = overhead for the item tag and length field
+        offset += fragment->getLength() + 8;
+
+        currentFragment++;
+        fragment = pixelSequence_->nextInContainer(fragment);
+      }
+
+      if (currentFragment != countFragments ||
+          currentFrame + 1 != countFrames ||
+          fragment != NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      assert(startFragment_.size() == countFragments_.size() &&
+             startFragment_.size() == frameSize_.size());
+    }
+
+
+    virtual void GetRawFrame(std::string& frame,
+                             unsigned int index) const
+    {
+      if (index >= startFragment_.size())
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      frame.resize(frameSize_[index]);
+      if (frame.size() == 0)
+      {
+        return;
+      }
+
+      uint8_t* target = reinterpret_cast<uint8_t*>(&frame[0]);
+
+      size_t offset = 0;
+      DcmPixelItem* fragment = startFragment_[index];
+      for (unsigned int i = 0; i < countFragments_[index]; i++)
+      {
+        uint8_t* content = NULL;
+        if (!fragment->getUint8Array(content).good() ||
+            content == NULL)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        assert(offset + fragment->getLength() <= frame.size());
+
+        memcpy(target + offset, content, fragment->getLength());
+        offset += fragment->getLength();
+
+        fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment));
+      }
+    }
+  };
+
+
+
+  class DicomFrameIndex::UncompressedIndex : public DicomFrameIndex::IIndex
+  {
+  private:
+    uint8_t*  pixelData_;
+    size_t    frameSize_;
+
+  public: 
+    UncompressedIndex(DcmDataset& dataset,
+                      unsigned int countFrames,
+                      size_t frameSize) :
+      pixelData_(NULL),
+      frameSize_(frameSize)
+    {
+      size_t size = 0;
+
+      DcmElement* e;
+      if (dataset.findAndGetElement(DCM_PixelData, e).good() &&
+          e != NULL)
+      {
+        size = e->getLength();
+
+        if (size > 0)
+        {
+          pixelData_ = NULL;
+          if (!e->getUint8Array(pixelData_).good() ||
+              pixelData_ == NULL)
+          {
+            throw OrthancException(ErrorCode_BadFileFormat);
+          }
+        }
+      }
+
+      if (size < frameSize_ * countFrames)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+
+    virtual void GetRawFrame(std::string& frame,
+                             unsigned int index) const
+    {
+      frame.resize(frameSize_);
+      if (frameSize_ > 0)
+      {
+        memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_);
+      }
+    }
+  };
+
+
+  class DicomFrameIndex::PsmctRle1Index : public DicomFrameIndex::IIndex
+  {
+  private:
+    std::string  pixelData_;
+    size_t       frameSize_;
+
+  public: 
+    PsmctRle1Index(DcmDataset& dataset,
+                   unsigned int countFrames,
+                   size_t frameSize) :
+      frameSize_(frameSize)
+    {
+      if (!DicomImageDecoder::DecodePsmctRle1(pixelData_, dataset) ||
+          pixelData_.size() < frameSize * countFrames)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+
+    virtual void GetRawFrame(std::string& frame,
+                             unsigned int index) const
+    {
+      frame.resize(frameSize_);
+      if (frameSize_ > 0)
+      {
+        memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_);
+      }
+    }
+  };
+
+
+
+  bool DicomFrameIndex::IsVideo(DcmFileFormat& dicom)
+  {
+    // Retrieve the transfer syntax from the DICOM header
+    const char* value = NULL;
+    if (!dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() ||
+        value == NULL)
+    {
+      return false;
+    }
+
+    const std::string transferSyntax(value);
+
+    // Video standards supported in DICOM 2016a
+    // http://dicom.nema.org/medical/dicom/2016a/output/html/part05.html
+    if (transferSyntax == "1.2.840.10008.1.2.4.100" ||  // MPEG2 MP@ML option of ISO/IEC MPEG2
+        transferSyntax == "1.2.840.10008.1.2.4.101" ||  // MPEG2 MP@HL option of ISO/IEC MPEG2
+        transferSyntax == "1.2.840.10008.1.2.4.102" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.1 of ITU-T H.264
+        transferSyntax == "1.2.840.10008.1.2.4.103" ||  // MPEG-4 AVC/H.264 BD-compat High Profile / Level 4.1 of ITU-T H.264
+        transferSyntax == "1.2.840.10008.1.2.4.104" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264
+        transferSyntax == "1.2.840.10008.1.2.4.105" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264
+        transferSyntax == "1.2.840.10008.1.2.4.106")    // MPEG-4 AVC/H.264 Stereo High Profile / Level 4.2 of the ITU-T H.264
+    {
+      return true;
+    }
+
+    return false;
+  }
+
+
+  unsigned int DicomFrameIndex::GetFramesCount(DcmFileFormat& dicom)
+  {
+    // Assume 1 frame for video transfer syntaxes
+    if (IsVideo(dicom))
+    {
+      return 1;
+    }        
+
+    const char* tmp = NULL;
+    if (!dicom.getDataset()->findAndGetString(DCM_NumberOfFrames, tmp).good() ||
+        tmp == NULL)
+    {
+      return 1;
+    }
+
+    int count = -1;
+    try
+    {
+      count = boost::lexical_cast<int>(tmp);
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+    }
+
+    if (count < 0)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);        
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+  DicomFrameIndex::DicomFrameIndex(DcmFileFormat& dicom)
+  {
+    countFrames_ = GetFramesCount(dicom);
+    if (countFrames_ == 0)
+    {
+      // The image has no frame. No index is to be built.
+      return;
+    }
+
+    DcmDataset& dataset = *dicom.getDataset();
+
+    // Test whether this image is composed of a sequence of fragments
+    DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
+    if (pixelSequence != NULL)
+    {
+      index_.reset(new FragmentIndex(pixelSequence, countFrames_));
+      return;
+    }
+
+    // Extract information about the image structure
+    DicomMap tags;
+    FromDcmtkBridge::ExtractDicomSummary(tags, dataset);
+
+    DicomImageInformation information(tags);
+
+    // Access to the raw pixel data
+    if (DicomImageDecoder::IsPsmctRle1(dataset))
+    {
+      index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize()));
+    }
+    else
+    {
+      index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize()));
+    }
+  }
+
+
+  void DicomFrameIndex::GetRawFrame(std::string& frame,
+                                    unsigned int index) const
+  {
+    if (index >= countFrames_)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else if (index_.get() != NULL)
+    {
+      return index_->GetRawFrame(frame, index);
+    }
+    else
+    {
+      frame.clear();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/Internals/DicomFrameIndex.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Enumerations.h"
+
+#include <dcmtk/dcmdata/dcdatset.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <vector>
+#include <stdint.h>
+#include <boost/noncopyable.hpp>
+#include <memory>
+
+namespace Orthanc
+{
+  class DicomFrameIndex
+  {
+  private:
+    class IIndex : public boost::noncopyable
+    {
+    public:
+      virtual ~IIndex()
+      {
+      }
+
+      virtual void GetRawFrame(std::string& frame,
+                               unsigned int index) const = 0;
+    };
+
+    class FragmentIndex;
+    class UncompressedIndex;
+    class PsmctRle1Index;
+
+    std::auto_ptr<IIndex>  index_;
+    unsigned int           countFrames_;
+
+  public:
+    DicomFrameIndex(DcmFileFormat& dicom);
+
+    unsigned int GetFramesCount() const
+    {
+      return countFrames_;
+    }
+
+    void GetRawFrame(std::string& frame,
+                     unsigned int index) const;
+
+    static bool IsVideo(DcmFileFormat& dicom);
+
+    static unsigned int GetFramesCount(DcmFileFormat& dicom);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,817 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../../PrecompiledHeaders.h"
+#include "DicomImageDecoder.h"
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project
+  (cf. function "DecodePsmctRle1()"):
+
+  Program: GDCM (Grassroots DICOM). A DICOM library
+  Module:  http://gdcm.sourceforge.net/Copyright.html
+
+  Copyright (c) 2006-2011 Mathieu Malaterre
+  Copyright (c) 1993-2005 CREATIS
+  (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image)
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+
+  * Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+  * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any
+  contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to
+  endorse or promote products derived from this software without specific
+  prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
+  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+  =========================================================================*/
+
+
+#include "../../Logging.h"
+#include "../../OrthancException.h"
+#include "../../Images/Image.h"
+#include "../../Images/ImageProcessing.h"
+#include "../../DicomFormat/DicomIntegerPixelAccessor.h"
+#include "../ToDcmtkBridge.h"
+#include "../FromDcmtkBridge.h"
+#include "../ParsedDicomFile.h"
+
+#if ORTHANC_ENABLE_PNG == 1
+#  include "../../Images/PngWriter.h"
+#endif
+
+#if ORTHANC_ENABLE_JPEG == 1
+#  include "../../Images/JpegWriter.h"
+#endif
+
+#include <boost/lexical_cast.hpp>
+
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcrleccd.h>
+#include <dcmtk/dcmdata/dcrlecp.h>
+
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+#  include <dcmtk/dcmjpls/djcodecd.h>
+#  include <dcmtk/dcmjpls/djcparam.h>
+#  include <dcmtk/dcmjpeg/djrplol.h>
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+#  include <dcmtk/dcmjpeg/djcodecd.h>
+#  include <dcmtk/dcmjpeg/djcparam.h>
+#  include <dcmtk/dcmjpeg/djdecbas.h>
+#  include <dcmtk/dcmjpeg/djdecext.h>
+#  include <dcmtk/dcmjpeg/djdeclol.h>
+#  include <dcmtk/dcmjpeg/djdecpro.h>
+#  include <dcmtk/dcmjpeg/djdecsps.h>
+#  include <dcmtk/dcmjpeg/djdecsv1.h>
+#endif
+
+#if DCMTK_VERSION_NUMBER <= 360
+#  define EXS_JPEGProcess1      EXS_JPEGProcess1TransferSyntax
+#  define EXS_JPEGProcess2_4    EXS_JPEGProcess2_4TransferSyntax
+#  define EXS_JPEGProcess6_8    EXS_JPEGProcess6_8TransferSyntax
+#  define EXS_JPEGProcess10_12  EXS_JPEGProcess10_12TransferSyntax
+#  define EXS_JPEGProcess14     EXS_JPEGProcess14TransferSyntax
+#  define EXS_JPEGProcess14SV1  EXS_JPEGProcess14SV1TransferSyntax
+#endif
+
+namespace Orthanc
+{
+  static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a);
+  static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011);
+
+
+  bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset)
+  {
+    DcmElement* e;
+    char* c;
+
+    // Check whether the DICOM instance contains an image encoded with
+    // the PMSCT_RLE1 scheme.
+    if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_COMPRESSION_TYPE), e).good() ||
+        e == NULL ||
+        !e->isaString() ||
+        !e->getString(c).good() ||
+        c == NULL ||
+        strcmp("PMSCT_RLE1", c))
+    {
+      return false;
+    }
+    else
+    {
+      return true;
+    }
+  }
+
+
+  bool DicomImageDecoder::DecodePsmctRle1(std::string& output,
+                                          DcmDataset& dataset)
+  {
+    // Check whether the DICOM instance contains an image encoded with
+    // the PMSCT_RLE1 scheme.
+    if (!IsPsmctRle1(dataset))
+    {
+      return false;
+    }
+
+    // OK, this is a custom RLE encoding from Philips. Get the pixel
+    // data from the appropriate private DICOM tag.
+    Uint8* pixData = NULL;
+    DcmElement* e;
+    if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_CONTENT), e).good() ||
+        e == NULL ||
+        e->getUint8Array(pixData) != EC_Normal)
+    {
+      return false;
+    }    
+
+    // The "unsigned" below IS VERY IMPORTANT
+    const uint8_t* inbuffer = reinterpret_cast<const uint8_t*>(pixData);
+    const size_t length = e->getLength();
+
+    /**
+     * The code below is an adaptation of a sample code for GDCM by
+     * Mathieu Malaterre (under a BSD license).
+     * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html
+     **/
+
+    // RLE pass
+    std::vector<uint8_t> temp;
+    temp.reserve(length);
+    for (size_t i = 0; i < length; i++)
+    {
+      if (inbuffer[i] == 0xa5)
+      {
+        temp.push_back(inbuffer[i+2]);
+        for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--)
+        {
+          temp.push_back(inbuffer[i+2]);
+        }
+        i += 2;
+      }
+      else
+      {
+        temp.push_back(inbuffer[i]);
+      }
+    }
+
+    // Delta encoding pass
+    uint16_t delta = 0;
+    output.clear();
+    output.reserve(temp.size());
+    for (size_t i = 0; i < temp.size(); i++)
+    {
+      uint16_t value;
+
+      if (temp[i] == 0x5a)
+      {
+        uint16_t v1 = temp[i + 1];
+        uint16_t v2 = temp[i + 2];
+        value = (v2 << 8) + v1;
+        i += 2;
+      }
+      else
+      {
+        value = delta + (int8_t) temp[i];
+      }
+
+      output.push_back(value & 0xff);
+      output.push_back(value >> 8);
+      delta = value;
+    }
+
+    if (output.size() % 2)
+    {
+      output.resize(output.size() - 1);
+    }
+
+    return true;
+  }
+
+
+  class DicomImageDecoder::ImageSource
+  {
+  private:
+    std::string psmct_;
+    std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_;
+
+  public:
+    void Setup(DcmDataset& dataset,
+               unsigned int frame)
+    {
+      psmct_.clear();
+      slowAccessor_.reset(NULL);
+
+      // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data
+
+      DicomMap m;
+      FromDcmtkBridge::ExtractDicomSummary(m, dataset);
+
+      /**
+       * Create an accessor to the raw values of the DICOM image.
+       **/
+
+      DcmElement* e;
+      if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() &&
+          e != NULL)
+      {
+        Uint8* pixData = NULL;
+        if (e->getUint8Array(pixData) == EC_Normal)
+        {    
+          slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength()));
+        }
+      }
+      else if (DecodePsmctRle1(psmct_, dataset))
+      {
+        LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded";
+        Uint8* pixData = NULL;
+        if (psmct_.size() > 0)
+        {
+          pixData = reinterpret_cast<Uint8*>(&psmct_[0]);
+        }
+
+        slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size()));
+      }
+    
+      if (slowAccessor_.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      slowAccessor_->SetCurrentFrame(frame);
+    }
+
+    unsigned int GetWidth() const
+    {
+      assert(slowAccessor_.get() != NULL);
+      return slowAccessor_->GetInformation().GetWidth();
+    }
+
+    unsigned int GetHeight() const
+    {
+      assert(slowAccessor_.get() != NULL);
+      return slowAccessor_->GetInformation().GetHeight();
+    }
+
+    unsigned int GetChannelCount() const
+    {
+      assert(slowAccessor_.get() != NULL);
+      return slowAccessor_->GetInformation().GetChannelCount();
+    }
+
+    const DicomIntegerPixelAccessor& GetAccessor() const
+    {
+      assert(slowAccessor_.get() != NULL);
+      return *slowAccessor_;
+    }
+
+    unsigned int GetSize() const
+    {
+      assert(slowAccessor_.get() != NULL);
+      return slowAccessor_->GetSize();
+    }
+  };
+
+
+  ImageAccessor* DicomImageDecoder::CreateImage(DcmDataset& dataset,
+                                                bool ignorePhotometricInterpretation)
+  {
+    DicomMap m;
+    FromDcmtkBridge::ExtractDicomSummary(m, dataset);
+
+    DicomImageInformation info(m);
+    PixelFormat format;
+    
+    if (!info.ExtractPixelFormat(format, ignorePhotometricInterpretation))
+    {
+      LOG(WARNING) << "Unsupported DICOM image: " << info.GetBitsStored() 
+                   << "bpp, " << info.GetChannelCount() << " channels, " 
+                   << (info.IsSigned() ? "signed" : "unsigned")
+                   << (info.IsPlanar() ? ", planar, " : ", non-planar, ")
+                   << EnumerationToString(info.GetPhotometricInterpretation())
+                   << " photometric interpretation";
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    return new Image(format, info.GetWidth(), info.GetHeight(), false);
+  }
+
+
+  template <typename PixelType>
+  static void CopyPixels(ImageAccessor& target,
+                         const DicomIntegerPixelAccessor& source)
+  {
+    const PixelType minValue = std::numeric_limits<PixelType>::min();
+    const PixelType maxValue = std::numeric_limits<PixelType>::max();
+
+    for (unsigned int y = 0; y < source.GetInformation().GetHeight(); y++)
+    {
+      PixelType* pixel = reinterpret_cast<PixelType*>(target.GetRow(y));
+      for (unsigned int x = 0; x < source.GetInformation().GetWidth(); x++)
+      {
+        for (unsigned int c = 0; c < source.GetInformation().GetChannelCount(); c++, pixel++)
+        {
+          int32_t v = source.GetValue(x, y, c);
+          if (v < static_cast<int32_t>(minValue))
+          {
+            *pixel = minValue;
+          }
+          else if (v > static_cast<int32_t>(maxValue))
+          {
+            *pixel = maxValue;
+          }
+          else
+          {
+            *pixel = static_cast<PixelType>(v);
+          }
+        }
+      }
+    }
+  }
+
+
+  ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset,
+                                                            unsigned int frame)
+  {
+    ImageSource source;
+    source.Setup(dataset, frame);
+
+
+    /**
+     * Resize the target image.
+     **/
+
+    std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false));
+
+    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.
+     **/
+
+    const DicomImageInformation& info = source.GetAccessor().GetInformation();
+
+    bool fastVersionSuccess = false;
+    PixelFormat sourceFormat;
+    if (!info.IsPlanar() &&
+        info.ExtractPixelFormat(sourceFormat, false))
+    {
+      try
+      {
+        size_t frameSize = info.GetHeight() * info.GetWidth() * GetBytesPerPixel(sourceFormat);
+        if ((frame + 1) * frameSize <= source.GetSize())
+        {
+          const uint8_t* buffer = reinterpret_cast<const uint8_t*>(source.GetAccessor().GetPixelData());
+
+          ImageAccessor sourceImage;
+          sourceImage.AssignReadOnly(sourceFormat, 
+                                     info.GetWidth(), 
+                                     info.GetHeight(),
+                                     info.GetWidth() * GetBytesPerPixel(sourceFormat),
+                                     buffer + frame * frameSize);
+
+          ImageProcessing::Convert(*target, sourceImage);
+          ImageProcessing::ShiftRight(*target, info.GetShift());
+          fastVersionSuccess = true;
+        }
+      }
+      catch (OrthancException&)
+      {
+        // Unsupported conversion, use the slow version
+      }
+    }
+
+    /**
+     * Slow version : loop over the DICOM buffer, storing its value
+     * into the target image.
+     **/
+
+    if (!fastVersionSuccess)
+    {
+      switch (target->GetFormat())
+      {
+        case PixelFormat_RGB24:
+        case PixelFormat_RGBA32:
+        case PixelFormat_Grayscale8:
+          CopyPixels<uint8_t>(*target, source.GetAccessor());
+          break;
+        
+        case PixelFormat_Grayscale16:
+          CopyPixels<uint16_t>(*target, source.GetAccessor());
+          break;
+
+        case PixelFormat_SignedGrayscale16:
+          CopyPixels<int16_t>(*target, source.GetAccessor());
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    return target.release();
+  }
+
+
+  ImageAccessor* DicomImageDecoder::ApplyCodec(const DcmCodec& codec,
+                                               const DcmCodecParameter& parameters,
+                                               DcmDataset& dataset,
+                                               unsigned int frame)
+  {
+    DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
+    if (pixelSequence == NULL)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true));
+
+    Uint32 startFragment = 0;  // Default 
+    OFString decompressedColorModel;  // Out
+    DJ_RPLossless representationParameter;
+    OFCondition c = codec.decodeFrame(&representationParameter, 
+                                      pixelSequence, &parameters, 
+                                      &dataset, frame, startFragment, target->GetBuffer(), 
+                                      target->GetSize(), decompressedColorModel);
+
+    if (c.good())
+    {
+      return target.release();    
+    }
+    else
+    {
+      LOG(ERROR) << "Cannot decode an image";
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom,
+                                           unsigned int frame)
+  {
+    DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset();
+    E_TransferSyntax syntax = dataset.getOriginalXfer();
+
+    /**
+     * Deal with uncompressed, raw images.
+     * http://support.dcmtk.org/docs/dcxfer_8h-source.html
+     **/
+    if (syntax == EXS_Unknown ||
+        syntax == EXS_LittleEndianImplicit ||
+        syntax == EXS_BigEndianImplicit ||
+        syntax == EXS_LittleEndianExplicit ||
+        syntax == EXS_BigEndianExplicit)
+    {
+      return DecodeUncompressedImage(dataset, frame);
+    }
+
+
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
+    /**
+     * Deal with JPEG-LS images.
+     **/
+
+    if (syntax == EXS_JPEGLSLossless ||
+        syntax == EXS_JPEGLSLossy)
+    {
+      DJLSCodecParameter parameters;
+      std::auto_ptr<DJLSDecoderBase> decoder;
+
+      switch (syntax)
+      {
+        case EXS_JPEGLSLossless:
+          LOG(INFO) << "Decoding a JPEG-LS lossless DICOM image";
+          decoder.reset(new DJLSLosslessDecoder);
+          break;
+          
+        case EXS_JPEGLSLossy:
+          LOG(INFO) << "Decoding a JPEG-LS near-lossless DICOM image";
+          decoder.reset(new DJLSNearLosslessDecoder);
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    
+      return ApplyCodec(*decoder, parameters, dataset, frame);
+    }
+#endif
+
+
+#if ORTHANC_ENABLE_DCMTK_JPEG == 1
+    /**
+     * Deal with JPEG images.
+     **/
+
+    if (syntax == EXS_JPEGProcess1     ||  // DJDecoderBaseline
+        syntax == EXS_JPEGProcess2_4   ||  // DJDecoderExtended
+        syntax == EXS_JPEGProcess6_8   ||  // DJDecoderSpectralSelection (retired)
+        syntax == EXS_JPEGProcess10_12 ||  // DJDecoderProgressive (retired)
+        syntax == EXS_JPEGProcess14    ||  // DJDecoderLossless
+        syntax == EXS_JPEGProcess14SV1)    // DJDecoderP14SV1
+    {
+      // http://support.dcmtk.org/docs-snapshot/djutils_8h.html#a2a9695e5b6b0f5c45a64c7f072c1eb9d
+      DJCodecParameter parameters(
+        ECC_lossyYCbCr,  // Mode for color conversion for compression, Unused for decompression
+        EDC_photometricInterpretation,  // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr
+        EUC_default,     // Mode for UID creation, unused for decompression
+        EPC_default);    // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation
+      std::auto_ptr<DJCodecDecoder> decoder;
+
+      switch (syntax)
+      {
+        case EXS_JPEGProcess1:
+          LOG(INFO) << "Decoding a JPEG baseline (process 1) DICOM image";
+          decoder.reset(new DJDecoderBaseline);
+          break;
+          
+        case EXS_JPEGProcess2_4 :
+          LOG(INFO) << "Decoding a JPEG baseline (processes 2 and 4) DICOM image";
+          decoder.reset(new DJDecoderExtended);
+          break;
+          
+        case EXS_JPEGProcess6_8:   // Retired
+          LOG(INFO) << "Decoding a JPEG spectral section, nonhierarchical (processes 6 and 8) DICOM image";
+          decoder.reset(new DJDecoderSpectralSelection);
+          break;
+          
+        case EXS_JPEGProcess10_12:   // Retired
+          LOG(INFO) << "Decoding a JPEG full progression, nonhierarchical (processes 10 and 12) DICOM image";
+          decoder.reset(new DJDecoderProgressive);
+          break;
+          
+        case EXS_JPEGProcess14:
+          LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical (process 14) DICOM image";
+          decoder.reset(new DJDecoderLossless);
+          break;
+          
+        case EXS_JPEGProcess14SV1:
+          LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical, first-order prediction (process 14 selection value 1) DICOM image";
+          decoder.reset(new DJDecoderP14SV1);
+          break;
+          
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    
+      return ApplyCodec(*decoder, parameters, dataset, frame);      
+    }
+#endif
+
+
+    if (syntax == EXS_RLELossless)
+    {
+      LOG(INFO) << "Decoding a RLE lossless DICOM image";
+      DcmRLECodecParameter parameters;
+      DcmRLECodecDecoder decoder;
+      return ApplyCodec(decoder, parameters, dataset, frame);
+    }
+
+
+    /**
+     * This DICOM image format is not natively supported by
+     * Orthanc. As a last resort, try and decode it through DCMTK by
+     * converting its transfer syntax to Little Endian. This will
+     * result in higher memory consumption. This is actually the
+     * second example of the following page:
+     * http://support.dcmtk.org/docs/mod_dcmjpeg.html#Examples
+     **/
+    
+    {
+      LOG(INFO) << "Decoding a compressed image by converting its transfer syntax to Little Endian";
+
+      std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone()));
+      converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
+
+      if (converted->canWriteXfer(EXS_LittleEndianExplicit))
+      {
+        return DecodeUncompressedImage(*converted, frame);
+      }
+    }
+
+    LOG(ERROR) << "Cannot decode a DICOM image with the built-in decoder";
+    throw OrthancException(ErrorCode_BadFileFormat);
+  }
+
+
+  static bool IsColorImage(PixelFormat format)
+  {
+    return (format == PixelFormat_RGB24 ||
+            format == PixelFormat_RGBA32);
+  }
+
+
+  bool DicomImageDecoder::TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image,
+                                               PixelFormat format,
+                                               bool allowColorConversion)
+  {
+    // If specified, prevent the conversion between color and
+    // grayscale images
+    bool isSourceColor = IsColorImage(image->GetFormat());
+    bool isTargetColor = IsColorImage(format);
+
+    if (!allowColorConversion)
+    {
+      if (isSourceColor ^ isTargetColor)
+      {
+        return false;
+      }
+    }
+
+    if (image->GetFormat() != format)
+    {
+      // A conversion is required
+      std::auto_ptr<ImageAccessor> target(new Image(format, image->GetWidth(), image->GetHeight(), false));
+      ImageProcessing::Convert(*target, *image);
+      image = target;
+    }
+
+    return true;
+  }
+
+
+  bool DicomImageDecoder::PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image)
+  {
+    switch (image->GetFormat())
+    {
+      case PixelFormat_RGB24:
+      {
+        // Directly return color images without modification (RGB)
+        return true;
+      }
+
+      case PixelFormat_Grayscale8:
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+      {
+        // Grayscale image: Stretch its dynamics to the [0,255] range
+        int64_t a, b;
+        ImageProcessing::GetMinMaxValue(a, b, *image);
+
+        if (a == b)
+        {
+          ImageProcessing::Set(*image, 0);
+        }
+        else
+        {
+          ImageProcessing::ShiftScale(*image, static_cast<float>(-a), 255.0f / static_cast<float>(b - a));
+        }
+
+        // If the source image is not grayscale 8bpp, convert it
+        if (image->GetFormat() != PixelFormat_Grayscale8)
+        {
+          std::auto_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false));
+          ImageProcessing::Convert(*target, *image);
+          image = target;
+        }
+
+        return true;
+      }
+      
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image,
+                                              ImageExtractionMode mode,
+                                              bool invert)
+  {
+    if (image.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    bool ok = false;
+
+    switch (mode)
+    {
+      case ImageExtractionMode_UInt8:
+        ok = TruncateDecodedImage(image, PixelFormat_Grayscale8, false);
+        break;
+
+      case ImageExtractionMode_UInt16:
+        ok = TruncateDecodedImage(image, PixelFormat_Grayscale16, false);
+        break;
+
+      case ImageExtractionMode_Int16:
+        ok = TruncateDecodedImage(image, PixelFormat_SignedGrayscale16, false);
+        break;
+
+      case ImageExtractionMode_Preview:
+        ok = PreviewDecodedImage(image);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (ok)
+    {
+      assert(image.get() != NULL);
+
+      if (invert)
+      {
+        Orthanc::ImageProcessing::Invert(*image);
+      }
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+#if ORTHANC_ENABLE_PNG == 1
+  void DicomImageDecoder::ExtractPngImage(std::string& result,
+                                          std::auto_ptr<ImageAccessor>& image,
+                                          ImageExtractionMode mode,
+                                          bool invert)
+  {
+    ApplyExtractionMode(image, mode, invert);
+
+    PngWriter writer;
+    writer.WriteToMemory(result, *image);
+  }
+#endif
+
+
+#if ORTHANC_ENABLE_JPEG == 1
+  void DicomImageDecoder::ExtractJpegImage(std::string& result,
+                                           std::auto_ptr<ImageAccessor>& image,
+                                           ImageExtractionMode mode,
+                                           bool invert,
+                                           uint8_t quality)
+  {
+    if (mode != ImageExtractionMode_UInt8 &&
+        mode != ImageExtractionMode_Preview)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    ApplyExtractionMode(image, mode, invert);
+
+    JpegWriter writer;
+    writer.SetQuality(quality);
+    writer.WriteToMemory(result, *image);
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/Internals/DicomImageDecoder.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,117 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../ParsedDicomFile.h"
+
+#include <memory>
+
+#if !defined(ORTHANC_ENABLE_JPEG)
+#  error The macro ORTHANC_ENABLE_JPEG must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_PNG)
+#  error The macro ORTHANC_ENABLE_PNG must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG)
+#  error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS)
+#  error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined
+#endif
+
+
+class DcmDataset;
+class DcmCodec;
+class DcmCodecParameter;
+
+namespace Orthanc
+{
+  class DicomImageDecoder : public boost::noncopyable
+  {
+  private:
+    class ImageSource;
+
+    DicomImageDecoder()   // This is a fully abstract class, no constructor
+    {
+    }
+
+    static ImageAccessor* CreateImage(DcmDataset& dataset,
+                                      bool ignorePhotometricInterpretation);
+
+    static ImageAccessor* DecodeUncompressedImage(DcmDataset& dataset,
+                                                  unsigned int frame);
+
+    static ImageAccessor* ApplyCodec(const DcmCodec& codec,
+                                     const DcmCodecParameter& parameters,
+                                     DcmDataset& dataset,
+                                     unsigned int frame);
+
+    static bool TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image,
+                                     PixelFormat format,
+                                     bool allowColorConversion);
+
+    static bool PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image);
+
+    static void ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image,
+                                    ImageExtractionMode mode,
+                                    bool invert);
+
+  public:
+    static bool IsPsmctRle1(DcmDataset& dataset);
+
+    static bool DecodePsmctRle1(std::string& output,
+                                DcmDataset& dataset);
+
+    static ImageAccessor *Decode(ParsedDicomFile& dicom,
+                                 unsigned int frame);
+
+#if ORTHANC_ENABLE_PNG == 1
+    static void ExtractPngImage(std::string& result,
+                                std::auto_ptr<ImageAccessor>& image,
+                                ImageExtractionMode mode,
+                                bool invert);
+#endif
+
+#if ORTHANC_ENABLE_JPEG == 1
+    static void ExtractJpegImage(std::string& result,
+                                 std::auto_ptr<ImageAccessor>& image,
+                                 ImageExtractionMode mode,
+                                 bool invert,
+                                 uint8_t quality);
+#endif
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/ParsedDicomFile.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,1476 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: GDCM (Grassroots DICOM). A DICOM library
+  Module:  http://gdcm.sourceforge.net/Copyright.html
+
+Copyright (c) 2006-2011 Mathieu Malaterre
+Copyright (c) 1993-2005 CREATIS
+(CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any
+   contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to
+   endorse or promote products derived from this software without specific
+   prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
+#include "../PrecompiledHeaders.h"
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#include "ParsedDicomFile.h"
+
+#include "FromDcmtkBridge.h"
+#include "ToDcmtkBridge.h"
+#include "Internals/DicomFrameIndex.h"
+#include "../Logging.h"
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+#include "../SystemToolbox.h"
+
+#if ORTHANC_ENABLE_JPEG == 1
+#  include "../Images/JpegReader.h"
+#endif
+
+#if ORTHANC_ENABLE_PNG == 1
+#  include "../Images/PngReader.h"
+#endif
+
+#include <list>
+#include <limits>
+
+#include <boost/lexical_cast.hpp>
+
+#include <dcmtk/dcmdata/dcchrstr.h>
+#include <dcmtk/dcmdata/dcdicent.h>
+#include <dcmtk/dcmdata/dcdict.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcuid.h>
+#include <dcmtk/dcmdata/dcmetinf.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+
+#include <dcmtk/dcmdata/dcvrae.h>
+#include <dcmtk/dcmdata/dcvras.h>
+#include <dcmtk/dcmdata/dcvrcs.h>
+#include <dcmtk/dcmdata/dcvrda.h>
+#include <dcmtk/dcmdata/dcvrds.h>
+#include <dcmtk/dcmdata/dcvrdt.h>
+#include <dcmtk/dcmdata/dcvrfd.h>
+#include <dcmtk/dcmdata/dcvrfl.h>
+#include <dcmtk/dcmdata/dcvris.h>
+#include <dcmtk/dcmdata/dcvrlo.h>
+#include <dcmtk/dcmdata/dcvrlt.h>
+#include <dcmtk/dcmdata/dcvrpn.h>
+#include <dcmtk/dcmdata/dcvrsh.h>
+#include <dcmtk/dcmdata/dcvrsl.h>
+#include <dcmtk/dcmdata/dcvrss.h>
+#include <dcmtk/dcmdata/dcvrst.h>
+#include <dcmtk/dcmdata/dcvrtm.h>
+#include <dcmtk/dcmdata/dcvrui.h>
+#include <dcmtk/dcmdata/dcvrul.h>
+#include <dcmtk/dcmdata/dcvrus.h>
+#include <dcmtk/dcmdata/dcvrut.h>
+#include <dcmtk/dcmdata/dcpixel.h>
+#include <dcmtk/dcmdata/dcpixseq.h>
+#include <dcmtk/dcmdata/dcpxitem.h>
+
+
+#include <boost/math/special_functions/round.hpp>
+#include <dcmtk/dcmdata/dcostrmb.h>
+#include <boost/algorithm/string/predicate.hpp>
+
+
+#if DCMTK_VERSION_NUMBER <= 360
+#  define EXS_JPEGProcess1      EXS_JPEGProcess1TransferSyntax
+#endif
+
+
+static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
+
+
+
+namespace Orthanc
+{
+  struct ParsedDicomFile::PImpl
+  {
+    std::auto_ptr<DcmFileFormat> file_;
+    std::auto_ptr<DicomFrameIndex>  frameIndex_;
+  };
+
+
+  static void SendPathValueForDictionary(RestApiOutput& output,
+                                         DcmItem& dicom)
+  {
+    Json::Value v = Json::arrayValue;
+
+    for (unsigned long i = 0; i < dicom.card(); i++)
+    {
+      DcmElement* element = dicom.getElement(i);
+      if (element)
+      {
+        char buf[16];
+        sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag());
+        v.append(buf);
+      }
+    }
+
+    output.AnswerJson(v);
+  }
+
+  static inline uint16_t GetCharValue(char c)
+  {
+    if (c >= '0' && c <= '9')
+      return c - '0';
+    else if (c >= 'a' && c <= 'f')
+      return c - 'a' + 10;
+    else if (c >= 'A' && c <= 'F')
+      return c - 'A' + 10;
+    else
+      return 0;
+  }
+
+  static inline uint16_t GetTagValue(const char* c)
+  {
+    return ((GetCharValue(c[0]) << 12) + 
+            (GetCharValue(c[1]) << 8) + 
+            (GetCharValue(c[2]) << 4) + 
+            GetCharValue(c[3]));
+  }
+
+  static void ParseTagAndGroup(DcmTagKey& key,
+                               const std::string& tag)
+  {
+    DicomTag t = FromDcmtkBridge::ParseTag(tag);
+    key = DcmTagKey(t.GetGroup(), t.GetElement());
+  }
+
+
+  static void SendSequence(RestApiOutput& output,
+                           DcmSequenceOfItems& sequence)
+  {
+    // This element is a sequence
+    Json::Value v = Json::arrayValue;
+
+    for (unsigned long i = 0; i < sequence.card(); i++)
+    {
+      v.append(boost::lexical_cast<std::string>(i));
+    }
+
+    output.AnswerJson(v);
+  }
+
+
+  static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData,
+                                             E_TransferSyntax transferSyntax)
+  {
+    DcmPixelSequence* pixelSequence = NULL;
+    if (pixelData.getEncapsulatedRepresentation
+        (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
+    {
+      return pixelSequence->card();
+    }
+    else
+    {
+      return 1;
+    }
+  }
+
+
+  namespace
+  {
+    class DicomFieldStream : public IHttpStreamAnswer
+    {
+    private:
+      DcmElement&  element_;
+      uint32_t     length_;
+      uint32_t     offset_;
+      std::string  chunk_;
+      size_t       chunkSize_;
+      
+    public:
+      DicomFieldStream(DcmElement& element,
+                       E_TransferSyntax transferSyntax) :
+        element_(element),
+        length_(element.getLength(transferSyntax)),
+        offset_(0),
+        chunkSize_(0)
+      {
+        static const size_t CHUNK_SIZE = 64 * 1024;  // Use chunks of max 64KB
+        chunk_.resize(CHUNK_SIZE);
+      }
+
+      virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/,
+                                                   bool /*deflateAllowed*/)
+      {
+        // No support for compression
+        return HttpCompression_None;
+      }
+
+      virtual bool HasContentFilename(std::string& filename)
+      {
+        return false;
+      }
+
+      virtual std::string GetContentType()
+      {
+        return "";
+      }
+
+      virtual uint64_t  GetContentLength()
+      {
+        return length_;
+      }
+ 
+      virtual bool ReadNextChunk()
+      {
+        assert(offset_ <= length_);
+
+        if (offset_ == length_)
+        {
+          return false;
+        }
+        else
+        {
+          if (length_ - offset_ < chunk_.size())
+          {
+            chunkSize_ = length_ - offset_;
+          }
+          else
+          {
+            chunkSize_ = chunk_.size();
+          }
+
+          OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_);
+
+          offset_ += chunkSize_;
+
+          if (!cond.good())
+          {
+            LOG(ERROR) << "Error while sending a DICOM field: " << cond.text();
+            throw OrthancException(ErrorCode_InternalError);
+          }
+
+          return true;
+        }
+      }
+ 
+      virtual const char *GetChunkContent()
+      {
+        return chunk_.c_str();
+      }
+ 
+      virtual size_t GetChunkSize()
+      {
+        return chunkSize_;
+      }
+    };
+  }
+
+
+  static bool AnswerPixelData(RestApiOutput& output,
+                              DcmItem& dicom,
+                              E_TransferSyntax transferSyntax,
+                              const std::string* blockUri)
+  {
+    DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(),
+             DICOM_TAG_PIXEL_DATA.GetElement());
+
+    DcmElement *element = NULL;
+    if (!dicom.findAndGetElement(k, element).good() ||
+        element == NULL)
+    {
+      return false;
+    }
+
+    try
+    {
+      DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
+      if (blockUri == NULL)
+      {
+        // The user asks how many blocks are present in this pixel data
+        unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax);
+
+        Json::Value result(Json::arrayValue);
+        for (unsigned int i = 0; i < blocks; i++)
+        {
+          result.append(boost::lexical_cast<std::string>(i));
+        }
+        
+        output.AnswerJson(result);
+        return true;
+      }
+
+
+      unsigned int block = boost::lexical_cast<unsigned int>(*blockUri);
+
+      if (block < GetPixelDataBlockCount(pixelData, transferSyntax))
+      {
+        DcmPixelSequence* pixelSequence = NULL;
+        if (pixelData.getEncapsulatedRepresentation
+            (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
+        {
+          // This is the case for JPEG transfer syntaxes
+          if (block < pixelSequence->card())
+          {
+            DcmPixelItem* pixelItem = NULL;
+            if (pixelSequence->getItem(pixelItem, block).good() && pixelItem)
+            {
+              if (pixelItem->getLength() == 0)
+              {
+                output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM);
+                return true;
+              }
+
+              Uint8* buffer = NULL;
+              if (pixelItem->getUint8Array(buffer).good() && buffer)
+              {
+                output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM);
+                return true;
+              }
+            }
+          }
+        }
+        else
+        {
+          // This is the case for raw, uncompressed image buffers
+          assert(*blockUri == "0");
+          DicomFieldStream stream(*element, transferSyntax);
+          output.AnswerStream(stream);
+        }
+      }
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      // The URI entered by the user is not a number
+    }
+    catch (std::bad_cast&)
+    {
+      // This should never happen
+    }
+
+    return false;
+  }
+
+
+
+  static void SendPathValueForLeaf(RestApiOutput& output,
+                                   const std::string& tag,
+                                   DcmItem& dicom,
+                                   E_TransferSyntax transferSyntax)
+  {
+    DcmTagKey k;
+    ParseTagAndGroup(k, tag);
+
+    DcmSequenceOfItems* sequence = NULL;
+    if (dicom.findAndGetSequence(k, sequence).good() && 
+        sequence != NULL &&
+        sequence->getVR() == EVR_SQ)
+    {
+      SendSequence(output, *sequence);
+      return;
+    }
+
+    DcmElement* element = NULL;
+    if (dicom.findAndGetElement(k, element).good() && 
+        element != NULL &&
+        //element->getVR() != EVR_UNKNOWN &&  // This would forbid private tags
+        element->getVR() != EVR_SQ)
+    {
+      DicomFieldStream stream(*element, transferSyntax);
+      output.AnswerStream(stream);
+    }
+  }
+
+  void ParsedDicomFile::SendPathValue(RestApiOutput& output,
+                                      const UriComponents& uri)
+  {
+    DcmItem* dicom = pimpl_->file_->getDataset();
+    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
+
+    // Special case: Accessing the pixel data
+    if (uri.size() == 1 || 
+        uri.size() == 2)
+    {
+      DcmTagKey tag;
+      ParseTagAndGroup(tag, uri[0]);
+
+      if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() &&
+          tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement())
+      {
+        AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]);
+        return;
+      }
+    }        
+
+    // Go down in the tag hierarchy according to the URI
+    for (size_t pos = 0; pos < uri.size() / 2; pos++)
+    {
+      size_t index;
+      try
+      {
+        index = boost::lexical_cast<size_t>(uri[2 * pos + 1]);
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return;
+      }
+
+      DcmTagKey k;
+      DcmItem *child = NULL;
+      ParseTagAndGroup(k, uri[2 * pos]);
+      if (!dicom->findAndGetSequenceItem(k, child, index).good() ||
+          child == NULL)
+      {
+        return;
+      }
+
+      dicom = child;
+    }
+
+    // We have reached the end of the URI
+    if (uri.size() % 2 == 0)
+    {
+      SendPathValueForDictionary(output, *dicom);
+    }
+    else
+    {
+      SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax);
+    }
+  }
+
+
+  void ParsedDicomFile::Remove(const DicomTag& tag)
+  {
+    InvalidateCache();
+
+    DcmTagKey key(tag.GetGroup(), tag.GetElement());
+    DcmElement* element = pimpl_->file_->getDataset()->remove(key);
+    if (element != NULL)
+    {
+      delete element;
+    }
+  }
+
+
+  void ParsedDicomFile::Clear(const DicomTag& tag,
+                              bool onlyIfExists)
+  {
+    InvalidateCache();
+
+    DcmItem* dicom = pimpl_->file_->getDataset();
+    DcmTagKey key(tag.GetGroup(), tag.GetElement());
+
+    if (onlyIfExists &&
+        !dicom->tagExists(key))
+    {
+      // The tag is non-existing, do not clear it
+    }
+    else
+    {
+      if (!dicom->insertEmptyElement(key, OFTrue /* replace old value */).good())
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep)
+  {
+    InvalidateCache();
+
+    DcmDataset& dataset = *pimpl_->file_->getDataset();
+
+    // Loop over the dataset to detect its private tags
+    typedef std::list<DcmElement*> Tags;
+    Tags privateTags;
+
+    for (unsigned long i = 0; i < dataset.card(); i++)
+    {
+      DcmElement* element = dataset.getElement(i);
+      DcmTag tag(element->getTag());
+
+      // Is this a private tag?
+      if (tag.isPrivate())
+      {
+        bool remove = true;
+
+        // Check whether this private tag is to be kept
+        if (toKeep != NULL)
+        {
+          DicomTag tmp = FromDcmtkBridge::Convert(tag);
+          if (toKeep->find(tmp) != toKeep->end())
+          {
+            remove = false;  // Keep it
+          }
+        }
+            
+        if (remove)
+        {
+          privateTags.push_back(element);
+        }
+      }
+    }
+
+    // Loop over the detected private tags to remove them
+    for (Tags::iterator it = privateTags.begin(); 
+         it != privateTags.end(); ++it)
+    {
+      DcmElement* tmp = dataset.remove(*it);
+      if (tmp != NULL)
+      {
+        delete tmp;
+      }
+    }
+  }
+
+
+  static void InsertInternal(DcmDataset& dicom,
+                             DcmElement* element)
+  {
+    OFCondition cond = dicom.insert(element, false, false);
+    if (!cond.good())
+    {
+      // This field already exists
+      delete element;
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  void ParsedDicomFile::Insert(const DicomTag& tag,
+                               const Json::Value& value,
+                               bool decodeDataUriScheme)
+  {
+    if (pimpl_->file_->getDataset()->tagExists(ToDcmtkBridge::Convert(tag)))
+    {
+      throw OrthancException(ErrorCode_AlreadyExistingTag);
+    }
+
+    if (decodeDataUriScheme &&
+        value.type() == Json::stringValue &&
+        (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
+         tag == DICOM_TAG_PIXEL_DATA))
+    {
+      if (EmbedContentInternal(value.asString()))
+      {
+        return;
+      }
+    }
+
+    InvalidateCache();
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
+    InsertInternal(*pimpl_->file_->getDataset(), element.release());
+  }
+
+
+  static bool CanReplaceProceed(DcmDataset& dicom,
+                                const DcmTagKey& tag,
+                                DicomReplaceMode mode)
+  {
+    if (dicom.findAndDeleteElement(tag).good())
+    {
+      // This tag was existing, it has been deleted
+      return true;
+    }
+    else
+    {
+      // This tag was absent, act wrt. the specified "mode"
+      switch (mode)
+      {
+        case DicomReplaceMode_InsertIfAbsent:
+          return true;
+
+        case DicomReplaceMode_ThrowIfAbsent:
+          throw OrthancException(ErrorCode_InexistentItem);
+
+        case DicomReplaceMode_IgnoreIfAbsent:
+          return false;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+  }
+
+
+  void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag,
+                                         const std::string& utf8Value,
+                                         bool decodeDataUriScheme)
+  {
+    if (tag != DICOM_TAG_SOP_CLASS_UID &&
+        tag != DICOM_TAG_SOP_INSTANCE_UID)
+    {
+      return;
+    }
+
+    std::string binary;
+    const std::string* decoded = &utf8Value;
+
+    if (decodeDataUriScheme &&
+        boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
+    {
+      std::string mime;
+      if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value))
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      decoded = &binary;
+    }
+    else
+    {
+      Encoding encoding = GetEncoding();
+      if (GetEncoding() != Encoding_Utf8)
+      {
+        binary = Toolbox::ConvertFromUtf8(utf8Value, encoding);
+        decoded = &binary;
+      }
+    }
+
+    /**
+     * dcmodify will automatically correct 'Media Storage SOP Class
+     * UID' and 'Media Storage SOP Instance UID' in the metaheader, if
+     * you make changes to the related tags in the dataset ('SOP Class
+     * UID' and 'SOP Instance UID') via insert or modify mode
+     * options. You can disable this behaviour by using the -nmu
+     * option.
+     **/
+
+    if (tag == DICOM_TAG_SOP_CLASS_UID)
+    {
+      ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded);
+    }
+
+    if (tag == DICOM_TAG_SOP_INSTANCE_UID)
+    {
+      ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded);
+    }    
+  }
+
+
+  void ParsedDicomFile::Replace(const DicomTag& tag,
+                                const std::string& utf8Value,
+                                bool decodeDataUriScheme,
+                                DicomReplaceMode mode)
+  {
+    InvalidateCache();
+
+    DcmDataset& dicom = *pimpl_->file_->getDataset();
+    if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
+    {
+      // Either the tag was previously existing (and now removed), or
+      // the replace mode was set to "InsertIfAbsent"
+
+      if (decodeDataUriScheme &&
+          (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
+           tag == DICOM_TAG_PIXEL_DATA))
+      {
+        if (EmbedContentInternal(utf8Value))
+        {
+          return;
+        }
+      }
+
+      std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
+      FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, GetEncoding());
+
+      InsertInternal(dicom, element.release());
+      UpdateStorageUid(tag, utf8Value, false);
+    }
+  }
+
+    
+  void ParsedDicomFile::Replace(const DicomTag& tag,
+                                const Json::Value& value,
+                                bool decodeDataUriScheme,
+                                DicomReplaceMode mode)
+  {
+    InvalidateCache();
+
+    DcmDataset& dicom = *pimpl_->file_->getDataset();
+    if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
+    {
+      // Either the tag was previously existing (and now removed), or
+      // the replace mode was set to "InsertIfAbsent"
+
+      if (decodeDataUriScheme &&
+          value.type() == Json::stringValue &&
+          (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
+           tag == DICOM_TAG_PIXEL_DATA))
+      {
+        if (EmbedContentInternal(value.asString()))
+        {
+          return;
+        }
+      }
+
+      InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
+
+      if (tag == DICOM_TAG_SOP_CLASS_UID ||
+          tag == DICOM_TAG_SOP_INSTANCE_UID)
+      {
+        if (value.type() != Json::stringValue)
+        {
+          throw OrthancException(ErrorCode_BadParameterType);
+        }
+
+        UpdateStorageUid(tag, value.asString(), decodeDataUriScheme);
+      }
+    }
+  }
+
+    
+  void ParsedDicomFile::Answer(RestApiOutput& output)
+  {
+    std::string serialized;
+    if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *pimpl_->file_->getDataset()))
+    {
+      output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM);
+    }
+  }
+
+
+
+  bool ParsedDicomFile::GetTagValue(std::string& value,
+                                    const DicomTag& tag)
+  {
+    DcmTagKey k(tag.GetGroup(), tag.GetElement());
+    DcmDataset& dataset = *pimpl_->file_->getDataset();
+
+    if (tag.IsPrivate() ||
+        FromDcmtkBridge::IsUnknownTag(tag) ||
+        tag == DICOM_TAG_PIXEL_DATA ||
+        tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
+    {
+      const Uint8* data = NULL;   // This is freed in the destructor of the dataset
+      long unsigned int count = 0;
+
+      if (dataset.findAndGetUint8Array(k, data, &count).good())
+      {
+        if (count > 0)
+        {
+          assert(data != NULL);
+          value.assign(reinterpret_cast<const char*>(data), count);
+        }
+        else
+        {
+          value.clear();
+        }
+
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+    else
+    {
+      DcmElement* element = NULL;
+      if (!dataset.findAndGetElement(k, element).good() ||
+          element == NULL)
+      {
+        return false;
+      }
+
+      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
+                                  (*element, DicomToJsonFlags_Default, 
+                                   ORTHANC_MAXIMUM_TAG_LENGTH, GetEncoding()));
+      
+      if (v.get() == NULL ||
+          v->IsNull())
+      {
+        value = "";
+      }
+      else
+      {
+        // TODO v->IsBinary()
+        value = v->GetContent();
+      }
+      
+      return true;
+    }
+  }
+
+
+  DicomInstanceHasher ParsedDicomFile::GetHasher()
+  {
+    std::string patientId, studyUid, seriesUid, instanceUid;
+
+    if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) ||
+        !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) ||
+        !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) ||
+        !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid);
+  }
+
+
+  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);
+    SystemToolbox::WriteFile(content, path);
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(new DcmFileFormat);
+
+    if (createIdentifiers)
+    {
+      ReplacePlainString(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
+      ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
+      ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
+      ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
+    }
+  }
+
+
+  void ParsedDicomFile::CreateFromDicomMap(const DicomMap& source,
+                                           Encoding defaultEncoding)
+  {
+    pimpl_->file_.reset(new DcmFileFormat);
+
+    const DicomValue* tmp = source.TestAndGetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET);
+    if (tmp != NULL)
+    {
+      Encoding encoding;
+      if (tmp->IsNull() ||
+          tmp->IsBinary() ||
+          !GetDicomEncoding(encoding, tmp->GetContent().c_str()))
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        SetEncoding(encoding);
+      }
+    }
+    else
+    {
+      SetEncoding(defaultEncoding);
+    }
+
+    for (DicomMap::Map::const_iterator 
+           it = source.map_.begin(); it != source.map_.end(); ++it)
+    {
+      if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET &&
+          !it->second->IsNull())
+      {
+        ReplacePlainString(it->first, it->second->GetContent());
+      }
+    }
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(const DicomMap& map,
+                                   Encoding defaultEncoding) :
+    pimpl_(new PImpl)
+  {
+    CreateFromDicomMap(map, defaultEncoding);
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(const DicomMap& map) : 
+    pimpl_(new PImpl)
+  {
+    CreateFromDicomMap(map, GetDefaultDicomEncoding());
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(const void* content, 
+                                   size_t size) : pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(content, size));
+  }
+
+  ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl)
+  {
+    if (content.size() == 0)
+    {
+      pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(NULL, 0));
+    }
+    else
+    {
+      pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(&content[0], content.size()));
+    }
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other) : 
+    pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone()));
+
+    // Create a new instance-level identifier
+    ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(new DcmFileFormat(&dicom));
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(new DcmFileFormat(dicom));
+  }
+
+
+  ParsedDicomFile::~ParsedDicomFile()
+  {
+    delete pimpl_;
+  }
+
+
+  DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const
+  {
+    return *pimpl_->file_.get();
+  }
+
+
+  ParsedDicomFile* ParsedDicomFile::Clone()
+  {
+    return new ParsedDicomFile(*this);
+  }
+
+
+  bool ParsedDicomFile::EmbedContentInternal(const std::string& dataUriScheme)
+  {
+    std::string mime, content;
+    if (!Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme))
+    {
+      return false;
+    }
+
+    Toolbox::ToLowerCase(mime);
+
+    if (mime == "image/png")
+    {
+#if ORTHANC_ENABLE_PNG == 1
+      EmbedImage(mime, content);
+#else
+      LOG(ERROR) << "Orthanc was compiled without support of PNG";
+      throw OrthancException(ErrorCode_NotImplemented);
+#endif
+    }
+    else if (mime == "image/jpeg")
+    {
+#if ORTHANC_ENABLE_JPEG == 1
+      EmbedImage(mime, content);
+#else
+      LOG(ERROR) << "Orthanc was compiled without support of JPEG";
+      throw OrthancException(ErrorCode_NotImplemented);
+#endif
+    }
+    else if (mime == "application/pdf")
+    {
+      EmbedPdf(content);
+    }
+    else
+    {
+      LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime;
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    return true;
+  }
+
+
+  void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme)
+  {
+    if (!EmbedContentInternal(dataUriScheme))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+#if (ORTHANC_ENABLE_JPEG == 1 &&  \
+     ORTHANC_ENABLE_PNG == 1)
+  void ParsedDicomFile::EmbedImage(const std::string& mime,
+                                   const std::string& content)
+  {
+    if (mime == "image/png")
+    {
+      PngReader reader;
+      reader.ReadFromMemory(content);
+      EmbedImage(reader);
+    }
+    else if (mime == "image/jpeg")
+    {
+      JpegReader reader;
+      reader.ReadFromMemory(content);
+      EmbedImage(reader);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+#endif
+
+
+  void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor)
+  {
+    if (accessor.GetFormat() != PixelFormat_Grayscale8 &&
+        accessor.GetFormat() != PixelFormat_Grayscale16 &&
+        accessor.GetFormat() != PixelFormat_SignedGrayscale16 &&
+        accessor.GetFormat() != PixelFormat_RGB24 &&
+        accessor.GetFormat() != PixelFormat_RGBA32)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    InvalidateCache();
+
+    if (accessor.GetFormat() == PixelFormat_RGBA32)
+    {
+      LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM";
+    }
+
+    // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html
+
+    Remove(DICOM_TAG_PIXEL_DATA);
+    ReplacePlainString(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth()));
+    ReplacePlainString(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight()));
+    ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "1");
+    ReplacePlainString(DICOM_TAG_NUMBER_OF_FRAMES, "1");
+
+    if (accessor.GetFormat() == PixelFormat_SignedGrayscale16)
+    {
+      ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "1");
+    }
+    else
+    {
+      ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "0");  // Unsigned pixels
+    }
+
+    ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0");  // Color channels are interleaved
+    ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2");
+
+    unsigned int bytesPerPixel = 0;
+
+    switch (accessor.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8");
+        ReplacePlainString(DICOM_TAG_BITS_STORED, "8");
+        ReplacePlainString(DICOM_TAG_HIGH_BIT, "7");
+        bytesPerPixel = 1;
+        break;
+
+      case PixelFormat_RGB24:
+      case PixelFormat_RGBA32:
+        ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB");
+        ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3");
+        ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8");
+        ReplacePlainString(DICOM_TAG_BITS_STORED, "8");
+        ReplacePlainString(DICOM_TAG_HIGH_BIT, "7");
+        bytesPerPixel = 3;
+        break;
+
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+        ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16");
+        ReplacePlainString(DICOM_TAG_BITS_STORED, "16");
+        ReplacePlainString(DICOM_TAG_HIGH_BIT, "15");
+        bytesPerPixel = 2;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    assert(bytesPerPixel != 0);
+
+    DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), 
+               DICOM_TAG_PIXEL_DATA.GetElement());
+
+    std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key));
+
+    unsigned int pitch = accessor.GetWidth() * bytesPerPixel;
+    Uint8* target = NULL;
+    pixels->createUint8Array(accessor.GetHeight() * pitch, target);
+
+    for (unsigned int y = 0; y < accessor.GetHeight(); y++)
+    {
+      switch (accessor.GetFormat())
+      {
+        case PixelFormat_RGB24:
+        case PixelFormat_Grayscale8:
+        case PixelFormat_Grayscale16:
+        case PixelFormat_SignedGrayscale16:
+        {
+          memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch);
+          target += pitch;
+          break;
+        }
+
+        case PixelFormat_RGBA32:
+        {
+          // The alpha channel is not supported by the DICOM standard
+          const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y));
+          for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4)
+          {
+            target[0] = source[0];
+            target[1] = source[1];
+            target[2] = source[2];
+          }
+
+          break;
+        }
+          
+        default:
+          throw OrthancException(ErrorCode_NotImplemented);
+      }
+    }
+
+    if (!pimpl_->file_->getDataset()->insert(pixels.release(), false, false).good())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }    
+  }
+
+  
+  Encoding ParsedDicomFile::GetEncoding() const
+  {
+    return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset(),
+                                           GetDefaultDicomEncoding());
+  }
+
+
+  void ParsedDicomFile::SetEncoding(Encoding encoding)
+  {
+    if (encoding == Encoding_Windows1251)
+    {
+      // This Cyrillic codepage is not officially supported by the
+      // DICOM standard. Do not set the SpecificCharacterSet tag.
+      return;
+    }
+
+    std::string s = GetDicomSpecificCharacterSet(encoding);
+    ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s);
+  }
+
+  void ParsedDicomFile::DatasetToJson(Json::Value& target, 
+                                      DicomToJsonFormat format,
+                                      DicomToJsonFlags flags,
+                                      unsigned int maxStringLength)
+  {
+    FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(),
+                                        format, flags, maxStringLength, GetDefaultDicomEncoding());
+  }
+
+
+  void ParsedDicomFile::DatasetToJson(Json::Value& target)
+  {
+    FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset());
+  }
+
+
+  void ParsedDicomFile::HeaderToJson(Json::Value& target, 
+                                     DicomToJsonFormat format)
+  {
+    FromDcmtkBridge::ExtractHeaderAsJson(target, *pimpl_->file_->getMetaInfo(), format, DicomToJsonFlags_None, 0);
+  }
+
+
+  bool ParsedDicomFile::HasTag(const DicomTag& tag) const
+  {
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+    return pimpl_->file_->getDataset()->tagExists(key);
+  }
+
+
+  void ParsedDicomFile::EmbedPdf(const std::string& pdf)
+  {
+    if (pdf.size() < 5 ||  // (*)
+        strncmp("%PDF-", pdf.c_str(), 5) != 0)
+    {
+      LOG(ERROR) << "Not a PDF file";
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    InvalidateCache();
+
+    ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
+    ReplacePlainString(FromDcmtkBridge::Convert(DCM_Modality), "OT");
+    ReplacePlainString(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
+    ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf");
+    //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
+
+    std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument));
+
+    size_t s = pdf.size();
+    if (s & 1)
+    {
+      // The size of the buffer must be even
+      s += 1;
+    }
+
+    Uint8* bytes = NULL;
+    OFCondition result = element->createUint8Array(s, bytes);
+    if (!result.good() || bytes == NULL)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) )
+    bytes[s - 1] = 0;
+
+    memcpy(bytes, pdf.c_str(), pdf.size());
+      
+    DcmPolymorphOBOW* obj = element.release();
+    result = pimpl_->file_->getDataset()->insert(obj);
+
+    if (!result.good())
+    {
+      delete obj;
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+  }
+
+
+  bool ParsedDicomFile::ExtractPdf(std::string& pdf)
+  {
+    std::string sop, mime;
+    
+    if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) ||
+        !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) ||
+        sop != UID_EncapsulatedPDFStorage ||
+        mime != "application/pdf")
+    {
+      return false;
+    }
+
+    if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT))
+    {
+      return false;
+    }
+
+    // Strip the possible pad byte at the end of file, because the
+    // encapsulated documents must always have an even length. The PDF
+    // format expects files to end with %%EOF followed by CR/LF. If
+    // the last character of the file is not a CR or LF, we assume it
+    // is a pad byte and remove it.
+    if (pdf.size() > 0)
+    {
+      char last = *pdf.rbegin();
+
+      if (last != 10 && last != 13)
+      {
+        pdf.resize(pdf.size() - 1);
+      }
+    }
+
+    return true;
+  }
+
+
+  ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json,
+                                                   DicomFromJsonFlags flags)
+  {
+	const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false;
+	const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false;
+
+    std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers));
+    result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding()));
+
+    const Json::Value::Members tags = json.getMemberNames();
+    
+    for (size_t i = 0; i < tags.size(); i++)
+    {
+      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
+      const Json::Value& value = json[tags[i]];
+
+      if (tag == DICOM_TAG_PIXEL_DATA ||
+          tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
+      {
+        if (value.type() != Json::stringValue)
+        {
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+        else
+        {
+          result->EmbedContent(value.asString());
+        }
+      }
+      else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent);
+      }
+    }
+
+    return result.release();
+  }
+
+
+  void ParsedDicomFile::GetRawFrame(std::string& target,
+                                    std::string& mime,
+                                    unsigned int frameId)
+  {
+    if (pimpl_->frameIndex_.get() == NULL)
+    {
+      pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_));
+    }
+
+    pimpl_->frameIndex_->GetRawFrame(target, frameId);
+
+    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
+    switch (transferSyntax)
+    {
+      case EXS_JPEGProcess1:
+        mime = "image/jpeg";
+        break;
+       
+      case EXS_JPEG2000LosslessOnly:
+      case EXS_JPEG2000:
+        mime = "image/jp2";
+        break;
+
+      default:
+        mime = "application/octet-stream";
+        break;
+    }
+  }
+
+
+  void ParsedDicomFile::InvalidateCache()
+  {
+    pimpl_->frameIndex_.reset(NULL);
+  }
+
+
+  unsigned int ParsedDicomFile::GetFramesCount() const
+  {
+    return DicomFrameIndex::GetFramesCount(*pimpl_->file_);
+  }
+
+
+  void ParsedDicomFile::ChangeEncoding(Encoding target)
+  {
+    Encoding source = GetEncoding();
+
+    if (source != target)  // Avoid unnecessary conversion
+    {
+      ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target));
+      FromDcmtkBridge::ChangeStringEncoding(*pimpl_->file_->getDataset(), source, target);
+    }
+  }
+
+
+  void ParsedDicomFile::ExtractDicomSummary(DicomMap& target) const
+  {
+    FromDcmtkBridge::ExtractDicomSummary(target, *pimpl_->file_->getDataset());
+  }
+
+
+  void ParsedDicomFile::ExtractDicomAsJson(Json::Value& target) const
+  {
+    FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset());
+  }
+
+
+  bool ParsedDicomFile::LookupTransferSyntax(std::string& result)
+  {
+    return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_);
+  }
+
+
+  bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const
+  {
+    DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(),
+                DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement());
+
+    DcmDataset& dataset = *pimpl_->file_->getDataset();
+
+    const char *c = NULL;
+    if (dataset.findAndGetString(k, c).good() &&
+        c != NULL)
+    {
+      result = StringToPhotometricInterpretation(c);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/ParsedDicomFile.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,203 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../DicomFormat/DicomInstanceHasher.h"
+#include "../Images/ImageAccessor.h"
+#include "../IDynamicObject.h"
+#include "../RestApi/RestApiOutput.h"
+#include "../Toolbox.h"
+
+#if !defined(ORTHANC_ENABLE_JPEG)
+#  error Macro ORTHANC_ENABLE_JPEG must be defined to use this file
+#endif
+
+#if !defined(ORTHANC_ENABLE_PNG)
+#  error Macro ORTHANC_ENABLE_PNG must be defined to use this file
+#endif
+
+class DcmDataset;
+class DcmFileFormat;
+
+namespace Orthanc
+{
+  class ParsedDicomFile : public IDynamicObject
+  {
+  private:
+    struct PImpl;
+    PImpl* pimpl_;
+
+    ParsedDicomFile(ParsedDicomFile& other);
+
+    void CreateFromDicomMap(const DicomMap& source,
+                            Encoding defaultEncoding);
+
+    void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep);
+
+    void UpdateStorageUid(const DicomTag& tag,
+                          const std::string& value,
+                          bool decodeDataUriScheme);
+
+    void InvalidateCache();
+
+    bool EmbedContentInternal(const std::string& dataUriScheme);
+
+  public:
+    ParsedDicomFile(bool createIdentifiers);  // Create a minimal DICOM instance
+
+    ParsedDicomFile(const DicomMap& map,
+                    Encoding defaultEncoding);
+
+    ParsedDicomFile(const DicomMap& map);
+
+    ParsedDicomFile(const void* content,
+                    size_t size);
+
+    ParsedDicomFile(const std::string& content);
+
+    ParsedDicomFile(DcmDataset& dicom);
+
+    ParsedDicomFile(DcmFileFormat& dicom);
+
+    ~ParsedDicomFile();
+
+    DcmFileFormat& GetDcmtkObject() const;
+
+    ParsedDicomFile* Clone();
+
+    void SendPathValue(RestApiOutput& output,
+                       const UriComponents& uri);
+
+    void Answer(RestApiOutput& output);
+
+    void Remove(const DicomTag& tag);
+
+    // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization)
+    void Clear(const DicomTag& tag,
+               bool onlyIfExists);
+
+    void Replace(const DicomTag& tag,
+                 const std::string& utf8Value,
+                 bool decodeDataUriScheme,
+                 DicomReplaceMode mode);
+
+    void Replace(const DicomTag& tag,
+                 const Json::Value& value,  // Assumed to be encoded with UTF-8
+                 bool decodeDataUriScheme,
+                 DicomReplaceMode mode);
+
+    void Insert(const DicomTag& tag,
+                const Json::Value& value,   // Assumed to be encoded with UTF-8
+                bool decodeDataUriScheme);
+
+    void ReplacePlainString(const DicomTag& tag,
+                            const std::string& utf8Value)
+    {
+      Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent);
+    }
+
+    void RemovePrivateTags()
+    {
+      RemovePrivateTagsInternal(NULL);
+    }
+
+    void RemovePrivateTags(const std::set<DicomTag>& toKeep)
+    {
+      RemovePrivateTagsInternal(&toKeep);
+    }
+
+    // WARNING: This function handles the decoding of strings to UTF8
+    bool GetTagValue(std::string& value,
+                     const DicomTag& tag);
+
+    DicomInstanceHasher GetHasher();
+
+    void SaveToMemoryBuffer(std::string& buffer);
+
+    void SaveToFile(const std::string& path);
+
+    void EmbedContent(const std::string& dataUriScheme);
+
+    void EmbedImage(const ImageAccessor& accessor);
+
+#if (ORTHANC_ENABLE_JPEG == 1 &&  \
+     ORTHANC_ENABLE_PNG == 1)
+    void EmbedImage(const std::string& mime,
+                    const std::string& content);
+#endif
+
+    Encoding GetEncoding() const;
+
+    // WARNING: This function only sets the encoding, it will not
+    // convert the encoding of the tags. Use "ChangeEncoding()" if need be.
+    void SetEncoding(Encoding encoding);
+
+    void DatasetToJson(Json::Value& target, 
+                       DicomToJsonFormat format,
+                       DicomToJsonFlags flags,
+                       unsigned int maxStringLength);
+
+    // This version uses the default parameters for
+    // FileContentType_DicomAsJson
+    void DatasetToJson(Json::Value& target);
+
+    void HeaderToJson(Json::Value& target, 
+                      DicomToJsonFormat format);
+
+    bool HasTag(const DicomTag& tag) const;
+
+    void EmbedPdf(const std::string& pdf);
+
+    bool ExtractPdf(std::string& pdf);
+
+    void GetRawFrame(std::string& target, // OUT
+                     std::string& mime,   // OUT
+                     unsigned int frameId);  // IN
+
+    unsigned int GetFramesCount() const;
+
+    static ParsedDicomFile* CreateFromJson(const Json::Value& value,
+                                           DicomFromJsonFlags flags);
+
+    void ChangeEncoding(Encoding target);
+
+    void ExtractDicomSummary(DicomMap& target) const;
+
+    void ExtractDicomAsJson(Json::Value& target) const;
+
+    bool LookupTransferSyntax(std::string& result);
+
+    bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/ToDcmtkBridge.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,150 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "ToDcmtkBridge.h"
+
+#include <memory>
+#include <dcmtk/dcmnet/diutil.h>
+
+#include "../OrthancException.h"
+
+
+namespace Orthanc
+{
+  DcmEVR ToDcmtkBridge::Convert(ValueRepresentation vr)
+  {
+    switch (vr)
+    {
+      case ValueRepresentation_ApplicationEntity:
+        return EVR_AE;
+
+      case ValueRepresentation_AgeString:
+        return EVR_AS;
+
+      case ValueRepresentation_AttributeTag:
+        return EVR_AT;
+
+      case ValueRepresentation_CodeString:
+        return EVR_CS;
+
+      case ValueRepresentation_Date:
+        return EVR_DA;
+
+      case ValueRepresentation_DecimalString:
+        return EVR_DS;
+
+      case ValueRepresentation_DateTime:
+        return EVR_DT;
+
+      case ValueRepresentation_FloatingPointSingle:
+        return EVR_FL;
+
+      case ValueRepresentation_FloatingPointDouble:
+        return EVR_FD;
+
+      case ValueRepresentation_IntegerString:
+        return EVR_IS;
+
+      case ValueRepresentation_LongString:
+        return EVR_LO;
+
+      case ValueRepresentation_LongText:
+        return EVR_LT;
+
+      case ValueRepresentation_OtherByte:
+        return EVR_OB;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case ValueRepresentation_OtherDouble:
+          return EVR_OD;*/
+
+      case ValueRepresentation_OtherFloat:
+        return EVR_OF;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case ValueRepresentation_OtherLong:
+          return EVR_OL;*/
+
+      case ValueRepresentation_OtherWord:
+        return EVR_OW;
+
+      case ValueRepresentation_PersonName:
+        return EVR_PN;
+
+      case ValueRepresentation_ShortString:
+        return EVR_SH;
+
+      case ValueRepresentation_SignedLong:
+        return EVR_SL;
+
+      case ValueRepresentation_Sequence:
+        return EVR_SQ;
+
+      case ValueRepresentation_SignedShort:
+        return EVR_SS;
+
+      case ValueRepresentation_ShortText:
+        return EVR_ST;
+
+      case ValueRepresentation_Time:
+        return EVR_TM;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case ValueRepresentation_UnlimitedCharacters:
+          return EVR_UC;*/
+
+      case ValueRepresentation_UniqueIdentifier:
+        return EVR_UI;
+
+      case ValueRepresentation_UnsignedLong:
+        return EVR_UL;
+
+      case ValueRepresentation_Unknown:
+        return EVR_UN;
+
+        // Not supported as of DCMTK 3.6.0
+        /*case ValueRepresentation_UniversalResource:
+          return EVR_UR;*/
+
+      case ValueRepresentation_UnsignedShort:
+        return EVR_US;
+
+      case ValueRepresentation_UnlimitedText:
+        return EVR_UT;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/ToDcmtkBridge.h	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,55 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_DCMTK != 1
+#  error The macro ORTHANC_ENABLE_DCMTK must be set to 1
+#endif
+
+#include "../DicomFormat/DicomMap.h"
+#include <dcmtk/dcmdata/dcdatset.h>
+
+namespace Orthanc
+{
+  class ToDcmtkBridge
+  {
+  public:
+    static DcmTagKey Convert(const DicomTag& tag)
+    {
+      return DcmTagKey(tag.GetGroup(), tag.GetElement());
+    }
+
+    static DcmEVR Convert(ValueRepresentation vr);
+  };
+}
--- a/Core/Enumerations.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/Core/Enumerations.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -758,6 +758,116 @@
   }
 
 
+  const char* EnumerationToString(ModalityManufacturer manufacturer)
+  {
+    switch (manufacturer)
+    {
+      case ModalityManufacturer_Generic:
+        return "Generic";
+
+      case ModalityManufacturer_GenericNoWildcardInDates:
+        return "GenericNoWildcardInDates";
+
+      case ModalityManufacturer_GenericNoUniversalWildcard:
+        return "GenericNoUniversalWildcard";
+
+      case ModalityManufacturer_StoreScp:
+        return "StoreScp";
+      
+      case ModalityManufacturer_ClearCanvas:
+        return "ClearCanvas";
+      
+      case ModalityManufacturer_Dcm4Chee:
+        return "Dcm4Chee";
+      
+      case ModalityManufacturer_Vitrea:
+        return "Vitrea";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(DicomRequestType type)
+  {
+    switch (type)
+    {
+      case DicomRequestType_Echo:
+        return "Echo";
+        break;
+
+      case DicomRequestType_Find:
+        return "Find";
+        break;
+
+      case DicomRequestType_Get:
+        return "Get";
+        break;
+
+      case DicomRequestType_Move:
+        return "Move";
+        break;
+
+      case DicomRequestType_Store:
+        return "Store";
+        break;
+
+      default: 
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(TransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      case TransferSyntax_Deflated:
+        return "Deflated";
+
+      case TransferSyntax_Jpeg:
+        return "JPEG";
+
+      case TransferSyntax_Jpeg2000:
+        return "JPEG2000";
+
+      case TransferSyntax_JpegLossless:
+        return "JPEG Lossless";
+
+      case TransferSyntax_Jpip:
+        return "JPIP";
+
+      case TransferSyntax_Mpeg2:
+        return "MPEG2";
+
+      case TransferSyntax_Rle:
+        return "RLE";
+
+      default: 
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(DicomVersion version)
+  {
+    switch (version)
+    {
+      case DicomVersion_2008:
+        return "2008";
+        break;
+
+      case DicomVersion_2017c:
+        return "2017c";
+        break;
+
+      default: 
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
   Encoding StringToEncoding(const char* encoding)
   {
     std::string s(encoding);
@@ -1127,6 +1237,86 @@
   }
   
 
+  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer)
+  {
+    ModalityManufacturer result;
+    bool obsolete = false;
+    
+    if (manufacturer == "Generic")
+    {
+      return ModalityManufacturer_Generic;
+    }
+    else if (manufacturer == "GenericNoWildcardInDates")
+    {
+      return ModalityManufacturer_GenericNoWildcardInDates;
+    }
+    else if (manufacturer == "GenericNoUniversalWildcard")
+    {
+      return ModalityManufacturer_GenericNoUniversalWildcard;
+    }
+    else if (manufacturer == "ClearCanvas")
+    {
+      return ModalityManufacturer_ClearCanvas;
+    }
+    else if (manufacturer == "StoreScp")
+    {
+      return ModalityManufacturer_StoreScp;
+    }
+    else if (manufacturer == "Dcm4Chee")
+    {
+      return ModalityManufacturer_Dcm4Chee;
+    }
+    else if (manufacturer == "Vitrea")
+    {
+      return ModalityManufacturer_Vitrea;
+    }
+    else if (manufacturer == "AgfaImpax" ||
+             manufacturer == "SyngoVia")
+    {
+      result = ModalityManufacturer_GenericNoWildcardInDates;
+      obsolete = true;
+    }
+    else if (manufacturer == "EFilm2" ||
+             manufacturer == "MedInria")
+    {
+      result = ModalityManufacturer_Generic;
+      obsolete = true;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (obsolete)
+    {
+      LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is obsolete since "
+                   << "Orthanc 1.3.0. To guarantee compatibility with future Orthanc "
+                   << "releases, you should replace it by \""
+                   << EnumerationToString(result)
+                   << "\" in your configuration file.";
+    }
+
+    return result;
+  }
+
+
+  DicomVersion StringToDicomVersion(const std::string& version)
+  {
+    if (version == "2008")
+    {
+      return DicomVersion_2008;
+    }
+    else if (version == "2017c")
+    {
+      return DicomVersion_2017c;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
   unsigned int GetBytesPerPixel(PixelFormat format)
   {
     switch (format)
--- a/Core/Enumerations.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/Core/Enumerations.h	Tue Aug 29 21:17:35 2017 +0200
@@ -443,6 +443,81 @@
     ValueRepresentation_NotSupported               // Not supported by Orthanc, or tag not in dictionary
   };
 
+  enum DicomReplaceMode
+  {
+    DicomReplaceMode_InsertIfAbsent,
+    DicomReplaceMode_ThrowIfAbsent,
+    DicomReplaceMode_IgnoreIfAbsent
+  };
+
+  enum DicomToJsonFormat
+  {
+    DicomToJsonFormat_Full,
+    DicomToJsonFormat_Short,
+    DicomToJsonFormat_Human
+  };
+
+  enum DicomToJsonFlags
+  {
+    DicomToJsonFlags_IncludeBinary         = (1 << 0),
+    DicomToJsonFlags_IncludePrivateTags    = (1 << 1),
+    DicomToJsonFlags_IncludeUnknownTags    = (1 << 2),
+    DicomToJsonFlags_IncludePixelData      = (1 << 3),
+    DicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),
+    DicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),
+
+    // Some predefined combinations
+    DicomToJsonFlags_None     = 0,
+    DicomToJsonFlags_Default  = (DicomToJsonFlags_IncludeBinary |
+                                 DicomToJsonFlags_IncludePixelData | 
+                                 DicomToJsonFlags_IncludePrivateTags | 
+                                 DicomToJsonFlags_IncludeUnknownTags | 
+                                 DicomToJsonFlags_ConvertBinaryToNull)
+  };
+  
+  enum DicomFromJsonFlags
+  {
+    DicomFromJsonFlags_DecodeDataUriScheme = (1 << 0),
+    DicomFromJsonFlags_GenerateIdentifiers = (1 << 1)
+  };
+  
+  enum DicomVersion
+  {
+    DicomVersion_2008,
+    DicomVersion_2017c
+  };
+
+  enum ModalityManufacturer
+  {
+    ModalityManufacturer_Generic,
+    ModalityManufacturer_GenericNoWildcardInDates,
+    ModalityManufacturer_GenericNoUniversalWildcard,
+    ModalityManufacturer_StoreScp,
+    ModalityManufacturer_ClearCanvas,
+    ModalityManufacturer_Dcm4Chee,
+    ModalityManufacturer_Vitrea
+  };
+
+  enum DicomRequestType
+  {
+    DicomRequestType_Echo,
+    DicomRequestType_Find,
+    DicomRequestType_Get,
+    DicomRequestType_Move,
+    DicomRequestType_Store
+  };
+
+  enum TransferSyntax
+  {
+    TransferSyntax_Deflated,
+    TransferSyntax_Jpeg,
+    TransferSyntax_Jpeg2000,
+    TransferSyntax_JpegLossless,
+    TransferSyntax_Jpip,
+    TransferSyntax_Mpeg2,
+    TransferSyntax_Rle
+  };
+
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
@@ -513,6 +588,14 @@
 
   const char* EnumerationToString(PixelFormat format);
 
+  const char* EnumerationToString(ModalityManufacturer manufacturer);
+
+  const char* EnumerationToString(DicomRequestType type);
+
+  const char* EnumerationToString(TransferSyntax syntax);
+
+  const char* EnumerationToString(DicomVersion version);
+
   Encoding StringToEncoding(const char* encoding);
 
   ResourceType StringToResourceType(const char* type);
@@ -525,6 +608,10 @@
                                                   bool throwIfUnsupported);
 
   PhotometricInterpretation StringToPhotometricInterpretation(const char* value);
+
+  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer);
+
+  DicomVersion StringToDicomVersion(const std::string& version);
   
   unsigned int GetBytesPerPixel(PixelFormat format);
 
--- a/Core/PrecompiledHeaders.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/Core/PrecompiledHeaders.h	Tue Aug 29 21:17:35 2017 +0200
@@ -50,7 +50,7 @@
 #include <json/value.h>
 
 #if ORTHANC_ENABLE_PUGIXML == 1
-#include <pugixml.hpp>
+#  include <pugixml.hpp>
 #endif
 
 #include "Enumerations.h"
@@ -58,4 +58,50 @@
 #include "OrthancException.h"
 #include "Toolbox.h"
 
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "DicomParsing/ParsedDicomFile.h"
+
+#  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>
 #endif
+
+#if ORTHANC_ENABLE_DCMTK_NETWORKING == 1
+#  include "DicomParsing/DicomServer.h"
+
+#  include <dcmtk/dcmnet/dcasccfg.h>
+#  include <dcmtk/dcmnet/diutil.h>
+#endif
+
+#endif
--- a/OrthancServer/DefaultDicomImageDecoder.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/DefaultDicomImageDecoder.h	Tue Aug 29 21:17:35 2017 +0200
@@ -34,8 +34,8 @@
 #pragma once
 
 #include "IDicomImageDecoder.h"
-#include "ParsedDicomFile.h"
-#include "Internals/DicomImageDecoder.h"
+#include "../Core/DicomParsing/ParsedDicomFile.h"
+#include "../Core/DicomParsing/Internals/DicomImageDecoder.h"
 
 namespace Orthanc
 {
--- a/OrthancServer/DicomDirWriter.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,510 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-
-
-
-/*=========================================================================
-
-  This file is based on portions of the following project:
-
-  Program: DCMTK 3.6.0
-  Module:  http://dicom.offis.de/dcmtk.php.en
-
-Copyright (C) 1994-2011, OFFIS e.V.
-All rights reserved.
-
-This software and supporting documentation were developed by
-
-  OFFIS e.V.
-  R&D Division Health
-  Escherweg 2
-  26121 Oldenburg, Germany
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-- Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-- Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-- Neither the name of OFFIS nor the names of its contributors may be
-  used to endorse or promote products derived from this software
-  without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-=========================================================================*/
-
-
-
-/***
-    
-    Validation:
-
-    # sudo apt-get install dicom3tools
-    # dciodvfy DICOMDIR 2>&1 | less
-    # dcentvfy DICOMDIR 2>&1 | less
-
-    http://www.dclunie.com/dicom3tools/dciodvfy.html
-
-    DICOMDIR viewer working with Wine under Linux:
-    http://www.microdicom.com/
-
- ***/
-
-
-#include "PrecompiledHeadersServer.h"
-#include "DicomDirWriter.h"
-
-#include "FromDcmtkBridge.h"
-#include "ToDcmtkBridge.h"
-
-#include "../Core/Logging.h"
-#include "../Core/OrthancException.h"
-#include "../Core/TemporaryFile.h"
-#include "../Core/Toolbox.h"
-#include "../Core/SystemToolbox.h"
-
-#include <dcmtk/dcmdata/dcdicdir.h>
-#include <dcmtk/dcmdata/dcmetinf.h>
-#include <dcmtk/dcmdata/dcdeftag.h>
-#include <dcmtk/dcmdata/dcuid.h>
-#include <dcmtk/dcmdata/dcddirif.h>
-#include <dcmtk/dcmdata/dcvrui.h>
-#include <dcmtk/dcmdata/dcsequen.h>
-#include <dcmtk/dcmdata/dcostrmf.h>
-#include "dcmtk/dcmdata/dcvrda.h"     /* for class DcmDate */
-#include "dcmtk/dcmdata/dcvrtm.h"     /* for class DcmTime */
-
-#include <memory>
-
-namespace Orthanc
-{
-  class DicomDirWriter::PImpl
-  {
-  private:
-    std::string fileSetId_;
-    TemporaryFile file_;
-    std::auto_ptr<DcmDicomDir> dir_;
-
-    typedef std::pair<ResourceType, std::string>  IndexKey;
-    typedef std::map<IndexKey, DcmDirectoryRecord* >  Index;
-    Index  index_;
-
-
-    DcmDicomDir& GetDicomDir()
-    {
-      if (dir_.get() == NULL)
-      {
-        dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), 
-                                   fileSetId_.c_str()));
-        //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8));
-      }
-
-      return *dir_;
-    }
-
-
-    DcmDirectoryRecord& GetRoot()
-    {
-      return GetDicomDir().getRootRecord();
-    }
-
-
-    static bool GetUtf8TagValue(std::string& result,
-                                DcmItem& source,
-                                Encoding encoding,
-                                const DcmTagKey& key)
-    {
-      DcmElement* element = NULL;
-
-      if (source.findAndGetElement(key, element).good())
-      {
-        char* s = NULL;
-        if (element->isLeaf() &&
-            element->getString(s).good() &&
-            s != NULL)
-        {
-          result = Toolbox::ConvertToUtf8(s, encoding);
-          return true;
-        }
-      }
-
-      result.clear();
-      return false;
-    }
-
-
-    static void SetTagValue(DcmDirectoryRecord& target,
-                            const DcmTagKey& key,
-                            const std::string& valueUtf8)
-    {
-      std::string s = Toolbox::ConvertFromUtf8(valueUtf8, Encoding_Ascii);
-
-      if (!target.putAndInsertString(key, s.c_str()).good())
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-                            
-
-
-    static bool CopyString(DcmDirectoryRecord& target,
-                           DcmDataset& source,
-                           Encoding encoding,
-                           const DcmTagKey& key,
-                           bool optional,
-                           bool copyEmpty)
-    {
-      if (optional &&
-          !source.tagExistsWithValue(key) &&
-          !(copyEmpty && source.tagExists(key)))
-      {
-        return false;
-      }
-
-      std::string value;
-      bool found = GetUtf8TagValue(value, source, encoding, key);
-
-      SetTagValue(target, key, value);
-      return found;
-    }
-
-
-    static void CopyStringType1(DcmDirectoryRecord& target,
-                                DcmDataset& source,
-                                Encoding encoding,
-                                const DcmTagKey& key)
-    {
-      CopyString(target, source, encoding, key, false, false);
-    }
-
-    static void CopyStringType1C(DcmDirectoryRecord& target,
-                                 DcmDataset& source,
-                                 Encoding encoding,
-                                 const DcmTagKey& key)
-    {
-      CopyString(target, source, encoding, key, true, false);
-    }
-
-    static void CopyStringType2(DcmDirectoryRecord& target,
-                                DcmDataset& source,
-                                Encoding encoding,
-                                const DcmTagKey& key)
-    {
-      CopyString(target, source, encoding, key, false, true);
-    }
-
-
-  public:
-    PImpl() : fileSetId_("ORTHANC_MEDIA")
-    {
-    }
-
-    void FillPatient(DcmDirectoryRecord& record,
-                     DcmDataset& dicom,
-                     Encoding encoding)
-    {
-      // cf. "DicomDirInterface::buildPatientRecord()"
-
-      CopyStringType1C(record, dicom, encoding, DCM_PatientID);
-      CopyStringType2(record, dicom, encoding, DCM_PatientName);
-    }
-
-    void FillStudy(DcmDirectoryRecord& record,
-                   DcmDataset& dicom,
-                   Encoding encoding)
-    {
-      // cf. "DicomDirInterface::buildStudyRecord()"
-
-      std::string nowDate, nowTime;
-      SystemToolbox::GetNowDicom(nowDate, nowTime);
-
-      std::string studyDate;
-      if (!GetUtf8TagValue(studyDate, dicom, encoding, DCM_StudyDate) &&
-          !GetUtf8TagValue(studyDate, dicom, encoding, DCM_SeriesDate) &&
-          !GetUtf8TagValue(studyDate, dicom, encoding, DCM_AcquisitionDate) &&
-          !GetUtf8TagValue(studyDate, dicom, encoding, DCM_ContentDate))
-      {
-        studyDate = nowDate;
-      }
-          
-      std::string studyTime;
-      if (!GetUtf8TagValue(studyTime, dicom, encoding, DCM_StudyTime) &&
-          !GetUtf8TagValue(studyTime, dicom, encoding, DCM_SeriesTime) &&
-          !GetUtf8TagValue(studyTime, dicom, encoding, DCM_AcquisitionTime) &&
-          !GetUtf8TagValue(studyTime, dicom, encoding, DCM_ContentTime))
-      {
-        studyTime = nowTime;
-      }
-
-      /* copy attribute values from dataset to study record */
-      SetTagValue(record, DCM_StudyDate, studyDate);
-      SetTagValue(record, DCM_StudyTime, studyTime);
-      CopyStringType2(record, dicom, encoding, DCM_StudyDescription);
-      CopyStringType1(record, dicom, encoding, DCM_StudyInstanceUID);
-      /* use type 1C instead of 1 in order to avoid unwanted overwriting */
-      CopyStringType1C(record, dicom, encoding, DCM_StudyID);
-      CopyStringType2(record, dicom, encoding, DCM_AccessionNumber);
-    }
-
-    void FillSeries(DcmDirectoryRecord& record,
-                    DcmDataset& dicom,
-                    Encoding encoding)
-    {
-      // cf. "DicomDirInterface::buildSeriesRecord()"
-
-      /* copy attribute values from dataset to series record */
-      CopyStringType1(record, dicom, encoding, DCM_Modality);
-      CopyStringType1(record, dicom, encoding, DCM_SeriesInstanceUID);
-      /* use type 1C instead of 1 in order to avoid unwanted overwriting */
-      CopyStringType1C(record, dicom, encoding, DCM_SeriesNumber);
-    }
-
-    void FillInstance(DcmDirectoryRecord& record,
-                      DcmDataset& dicom,
-                      Encoding encoding,
-                      DcmMetaInfo& metaInfo,
-                      const char* path)
-    {
-      // cf. "DicomDirInterface::buildImageRecord()"
-
-      /* copy attribute values from dataset to image record */
-      CopyStringType1(record, dicom, encoding, DCM_InstanceNumber);
-      //CopyElementType1C(record, dicom, encoding, DCM_ImageType);
-
-      // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record);
-
-      std::string sopClassUid, sopInstanceUid, transferSyntaxUid;
-      if (!GetUtf8TagValue(sopClassUid, dicom, encoding, DCM_SOPClassUID) ||
-          !GetUtf8TagValue(sopInstanceUid, dicom, encoding, DCM_SOPInstanceUID) ||
-          !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, DCM_TransferSyntaxUID))
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      SetTagValue(record, DCM_ReferencedFileID, path);
-      SetTagValue(record, DCM_ReferencedSOPClassUIDInFile, sopClassUid);
-      SetTagValue(record, DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUid);
-      SetTagValue(record, DCM_ReferencedTransferSyntaxUIDInFile, transferSyntaxUid);
-    }
-
-    
-
-    bool CreateResource(DcmDirectoryRecord*& target,
-                        ResourceType level,
-                        ParsedDicomFile& dicom,
-                        const char* filename,
-                        const char* path)
-    {
-      DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset();
-      Encoding encoding = dicom.GetEncoding();
-
-      bool found;
-      std::string id;
-      E_DirRecType type;
-
-      switch (level)
-      {
-        case ResourceType_Patient:
-          found = GetUtf8TagValue(id, dataset, encoding, DCM_PatientID);
-          type = ERT_Patient;
-          break;
-
-        case ResourceType_Study:
-          found = GetUtf8TagValue(id, dataset, encoding, DCM_StudyInstanceUID);
-          type = ERT_Study;
-          break;
-
-        case ResourceType_Series:
-          found = GetUtf8TagValue(id, dataset, encoding, DCM_SeriesInstanceUID);
-          type = ERT_Series;
-          break;
-
-        case ResourceType_Instance:
-          found = GetUtf8TagValue(id, dataset, encoding, DCM_SOPInstanceUID);
-          type = ERT_Image;
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-
-      if (!found)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      IndexKey key = std::make_pair(level, std::string(id.c_str()));
-      Index::iterator it = index_.find(key);
-
-      if (it != index_.end())
-      {
-        target = it->second;
-        return false; // Already existing
-      }
-
-      std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename));
-
-      switch (level)
-      {
-        case ResourceType_Patient:
-          FillPatient(*record, dataset, encoding);
-          break;
-
-        case ResourceType_Study:
-          FillStudy(*record, dataset, encoding);
-          break;
-
-        case ResourceType_Series:
-          FillSeries(*record, dataset, encoding);
-          break;
-
-        case ResourceType_Instance:
-          FillInstance(*record, dataset, encoding, *dicom.GetDcmtkObject().getMetaInfo(), path);
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-
-      CopyStringType1C(*record, dataset, encoding, DCM_SpecificCharacterSet);
-
-      target = record.get();
-      GetRoot().insertSub(record.release());
-      index_[key] = target;
-
-      return true;   // Newly created
-    }
-
-    void Read(std::string& s)
-    {
-      if (!GetDicomDir().write(DICOMDIR_DEFAULT_TRANSFERSYNTAX, 
-                               EET_UndefinedLength /*encodingType*/, 
-                               EGL_withoutGL /*groupLength*/).good())
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-
-      file_.Read(s);
-    }
-
-    void SetFileSetId(const std::string& id)
-    {
-      dir_.reset(NULL);
-      fileSetId_ = id;
-    }
-  };
-
-
-  DicomDirWriter::DicomDirWriter() : pimpl_(new PImpl)
-  {
-  }
-
-  DicomDirWriter::~DicomDirWriter()
-  {
-    if (pimpl_)
-    {
-      delete pimpl_;
-    }
-  }
-
-  void DicomDirWriter::SetFileSetId(const std::string& id)
-  {
-    pimpl_->SetFileSetId(id);
-  }
-
-  void DicomDirWriter::Add(const std::string& directory,
-                           const std::string& filename,
-                           ParsedDicomFile& dicom)
-  {
-    std::string path;
-    if (directory.empty())
-    {
-      path = filename;
-    }
-    else
-    {
-      if (directory[directory.length() - 1] == '/' ||
-          directory[directory.length() - 1] == '\\')
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-
-      path = directory + '\\' + filename;
-    }
-
-    DcmDirectoryRecord* instance;
-    bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, dicom, filename.c_str(), path.c_str());
-    if (isNewInstance)
-    {
-      DcmDirectoryRecord* series;
-      bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, dicom, filename.c_str(), NULL);
-      series->insertSub(instance);
-
-      if (isNewSeries)
-      {
-        DcmDirectoryRecord* study;
-        bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, dicom, filename.c_str(), NULL);
-        study->insertSub(series);
-  
-        if (isNewStudy)
-        {
-          DcmDirectoryRecord* patient;
-          pimpl_->CreateResource(patient, ResourceType_Patient, dicom, filename.c_str(), NULL);
-          patient->insertSub(study);
-        }
-      }
-    }
-  }
-
-  void DicomDirWriter::Encode(std::string& target)
-  {
-    pimpl_->Read(target);
-  }
-}
--- a/OrthancServer/DicomDirWriter.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ParsedDicomFile.h"
-
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  class DicomDirWriter : public boost::noncopyable
-  {
-  private:
-    class PImpl;
-    PImpl* pimpl_;
-
-  public:
-    DicomDirWriter();
-
-    ~DicomDirWriter();
-
-    void SetFileSetId(const std::string& id);
-
-    void Add(const std::string& directory,
-             const std::string& filename,
-             ParsedDicomFile& dicom);
-
-    void Encode(std::string& target);
-  };
-
-}
--- a/OrthancServer/DicomInstanceToStore.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/DicomInstanceToStore.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -34,7 +34,7 @@
 #include "PrecompiledHeadersServer.h"
 #include "DicomInstanceToStore.h"
 
-#include "FromDcmtkBridge.h"
+#include "../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../Core/Logging.h"
 
 #include <dcmtk/dcmdata/dcfilefo.h>
--- a/OrthancServer/DicomInstanceToStore.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/DicomInstanceToStore.h	Tue Aug 29 21:17:35 2017 +0200
@@ -33,7 +33,7 @@
 
 #pragma once
 
-#include "ParsedDicomFile.h"
+#include "../Core/DicomParsing/ParsedDicomFile.h"
 #include "ServerIndex.h"
 #include "../Core/OrthancException.h"
 #include "../Core/RestApi/RestApiCall.h"
--- a/OrthancServer/DicomModification.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1132 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeadersServer.h"
-#include "DicomModification.h"
-
-#include "../Core/Logging.h"
-#include "../Core/OrthancException.h"
-#include "FromDcmtkBridge.h"
-
-#include <memory>   // For std::auto_ptr
-
-
-static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2008 =
-  "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1";
-
-static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2017c =
-  "Orthanc " ORTHANC_VERSION " - PS 3.15-2017c Table E.1-1 Basic Profile";
-
-namespace Orthanc
-{
-  bool DicomModification::CancelReplacement(const DicomTag& tag)
-  {
-    Replacements::iterator it = replacements_.find(tag);
-    
-    if (it != replacements_.end())
-    {
-      delete it->second;
-      replacements_.erase(it);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  void DicomModification::ReplaceInternal(const DicomTag& tag,
-                                          const Json::Value& value)
-  {
-    Replacements::iterator it = replacements_.find(tag);
-
-    if (it != replacements_.end())
-    {
-      delete it->second;
-      it->second = NULL;   // In the case of an exception during the clone
-      it->second = new Json::Value(value);  // Clone
-    }
-    else
-    {
-      replacements_[tag] = new Json::Value(value);  // Clone
-    }
-  }
-
-
-  void DicomModification::ClearReplacements()
-  {
-    for (Replacements::iterator it = replacements_.begin();
-         it != replacements_.end(); ++it)
-    {
-      delete it->second;
-    }
-
-    replacements_.clear();
-  }
-
-
-  void DicomModification::MarkNotOrthancAnonymization()
-  {
-    Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD);
-
-    if (it != replacements_.end() &&
-        (it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2008 ||
-         it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2017c))
-    {
-      delete it->second;
-      replacements_.erase(it);
-    }
-  }
-
-
-  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, false /* don't try and decode data URI scheme for UIDs */, DicomReplaceMode_InsertIfAbsent);
-  }
-  
-  DicomModification::DicomModification() :
-    removePrivateTags_(false),
-    level_(ResourceType_Instance),
-    allowManualIdentifiers_(true),
-    keepStudyInstanceUid_(false),
-    keepSeriesInstanceUid_(false)
-  {
-  }
-
-  DicomModification::~DicomModification()
-  {
-    ClearReplacements();
-  }
-
-  void DicomModification::Keep(const DicomTag& tag)
-  {
-    bool wasRemoved = IsRemoved(tag);
-    bool wasCleared = IsCleared(tag);
-    
-    removals_.erase(tag);
-    clearings_.erase(tag);
-
-    bool wasReplaced = CancelReplacement(tag);
-
-    if (tag == DICOM_TAG_STUDY_INSTANCE_UID)
-    {
-      keepStudyInstanceUid_ = true;
-    }
-    else if (tag == DICOM_TAG_SERIES_INSTANCE_UID)
-    {
-      keepSeriesInstanceUid_ = true;
-    }
-    else if (tag.IsPrivate())
-    {
-      privateTagsToKeep_.insert(tag);
-    }
-    else if (!wasRemoved &&
-             !wasReplaced &&
-             !wasCleared)
-    {
-      LOG(WARNING) << "Marking this tag as to be kept has no effect: " << tag.Format();
-    }
-
-    MarkNotOrthancAnonymization();
-  }
-
-  void DicomModification::Remove(const DicomTag& tag)
-  {
-    removals_.insert(tag);
-    clearings_.erase(tag);
-    CancelReplacement(tag);
-    privateTagsToKeep_.erase(tag);
-
-    MarkNotOrthancAnonymization();
-  }
-
-  void DicomModification::Clear(const DicomTag& tag)
-  {
-    removals_.erase(tag);
-    clearings_.insert(tag);
-    CancelReplacement(tag);
-    privateTagsToKeep_.erase(tag);
-
-    MarkNotOrthancAnonymization();
-  }
-
-  bool DicomModification::IsRemoved(const DicomTag& tag) const
-  {
-    return removals_.find(tag) != removals_.end();
-  }
-
-  bool DicomModification::IsCleared(const DicomTag& tag) const
-  {
-    return clearings_.find(tag) != clearings_.end();
-  }
-
-  void DicomModification::Replace(const DicomTag& tag,
-                                  const Json::Value& value,
-                                  bool safeForAnonymization)
-  {
-    clearings_.erase(tag);
-    removals_.erase(tag);
-    privateTagsToKeep_.erase(tag);
-    ReplaceInternal(tag, value);
-
-    if (!safeForAnonymization)
-    {
-      MarkNotOrthancAnonymization();
-    }
-  }
-
-
-  bool DicomModification::IsReplaced(const DicomTag& tag) const
-  {
-    return replacements_.find(tag) != replacements_.end();
-  }
-
-  const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const
-  {
-    Replacements::const_iterator it = replacements_.find(tag);
-
-    if (it == replacements_.end())
-    {
-      throw OrthancException(ErrorCode_InexistentItem);
-    }
-    else
-    {
-      return *it->second;
-    } 
-  }
-
-
-  std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const
-  {
-    const Json::Value& json = GetReplacement(tag);
-
-    if (json.type() != Json::stringValue)
-    {
-      throw OrthancException(ErrorCode_BadParameterType);
-    }
-    else
-    {
-      return json.asString();
-    }    
-  }
-
-
-  void DicomModification::SetRemovePrivateTags(bool removed)
-  {
-    removePrivateTags_ = removed;
-
-    if (!removed)
-    {
-      MarkNotOrthancAnonymization();
-    }
-  }
-
-  void DicomModification::SetLevel(ResourceType level)
-  {
-    uidMap_.clear();
-    level_ = level;
-
-    if (level != ResourceType_Patient)
-    {
-      MarkNotOrthancAnonymization();
-    }
-  }
-
-
-  void DicomModification::SetupAnonymization2008()
-  {
-    // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles
-    // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2008/08_15pu.pdf
-    
-    removals_.insert(DicomTag(0x0008, 0x0014));  // Instance Creator UID
-    //removals_.insert(DicomTag(0x0008, 0x0018));  // SOP Instance UID => set in Apply()
-    removals_.insert(DicomTag(0x0008, 0x0050));  // Accession Number
-    removals_.insert(DicomTag(0x0008, 0x0080));  // Institution Name
-    removals_.insert(DicomTag(0x0008, 0x0081));  // Institution Address
-    removals_.insert(DicomTag(0x0008, 0x0090));  // Referring Physician's Name 
-    removals_.insert(DicomTag(0x0008, 0x0092));  // Referring Physician's Address 
-    removals_.insert(DicomTag(0x0008, 0x0094));  // Referring Physician's Telephone Numbers 
-    removals_.insert(DicomTag(0x0008, 0x1010));  // Station Name 
-    removals_.insert(DicomTag(0x0008, 0x1030));  // Study Description 
-    removals_.insert(DicomTag(0x0008, 0x103e));  // Series Description 
-    removals_.insert(DicomTag(0x0008, 0x1040));  // Institutional Department Name 
-    removals_.insert(DicomTag(0x0008, 0x1048));  // Physician(s) of Record 
-    removals_.insert(DicomTag(0x0008, 0x1050));  // Performing Physicians' Name 
-    removals_.insert(DicomTag(0x0008, 0x1060));  // Name of Physician(s) Reading Study 
-    removals_.insert(DicomTag(0x0008, 0x1070));  // Operators' Name 
-    removals_.insert(DicomTag(0x0008, 0x1080));  // Admitting Diagnoses Description 
-    removals_.insert(DicomTag(0x0008, 0x1155));  // Referenced SOP Instance UID 
-    removals_.insert(DicomTag(0x0008, 0x2111));  // Derivation Description 
-    //removals_.insert(DicomTag(0x0010, 0x0010));  // Patient's Name => cf. below (*)
-    //removals_.insert(DicomTag(0x0010, 0x0020));  // Patient ID => cf. below (*)
-    removals_.insert(DicomTag(0x0010, 0x0030));  // Patient's Birth Date 
-    removals_.insert(DicomTag(0x0010, 0x0032));  // Patient's Birth Time 
-    removals_.insert(DicomTag(0x0010, 0x0040));  // Patient's Sex 
-    removals_.insert(DicomTag(0x0010, 0x1000));  // Other Patient Ids 
-    removals_.insert(DicomTag(0x0010, 0x1001));  // Other Patient Names 
-    removals_.insert(DicomTag(0x0010, 0x1010));  // Patient's Age 
-    removals_.insert(DicomTag(0x0010, 0x1020));  // Patient's Size 
-    removals_.insert(DicomTag(0x0010, 0x1030));  // Patient's Weight 
-    removals_.insert(DicomTag(0x0010, 0x1090));  // Medical Record Locator 
-    removals_.insert(DicomTag(0x0010, 0x2160));  // Ethnic Group 
-    removals_.insert(DicomTag(0x0010, 0x2180));  // Occupation 
-    removals_.insert(DicomTag(0x0010, 0x21b0));  // Additional Patient's History 
-    removals_.insert(DicomTag(0x0010, 0x4000));  // Patient Comments 
-    removals_.insert(DicomTag(0x0018, 0x1000));  // Device Serial Number 
-    removals_.insert(DicomTag(0x0018, 0x1030));  // Protocol Name 
-    //removals_.insert(DicomTag(0x0020, 0x000d));  // Study Instance UID => set in Apply()
-    //removals_.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => set in Apply()
-    removals_.insert(DicomTag(0x0020, 0x0010));  // Study ID 
-    removals_.insert(DicomTag(0x0020, 0x0052));  // Frame of Reference UID 
-    removals_.insert(DicomTag(0x0020, 0x0200));  // Synchronization Frame of Reference UID 
-    removals_.insert(DicomTag(0x0020, 0x4000));  // Image Comments 
-    removals_.insert(DicomTag(0x0040, 0x0275));  // Request Attributes Sequence 
-    removals_.insert(DicomTag(0x0040, 0xa124));  // UID
-    removals_.insert(DicomTag(0x0040, 0xa730));  // Content Sequence 
-    removals_.insert(DicomTag(0x0088, 0x0140));  // Storage Media File-set UID 
-    removals_.insert(DicomTag(0x3006, 0x0024));  // Referenced Frame of Reference UID 
-    removals_.insert(DicomTag(0x3006, 0x00c2));  // Related Frame of Reference UID 
-
-    // Some more removals (from the experience of DICOM files at the CHU of Liege)
-    removals_.insert(DicomTag(0x0010, 0x1040));  // Patient's Address
-    removals_.insert(DicomTag(0x0032, 0x1032));  // Requesting Physician
-    removals_.insert(DicomTag(0x0010, 0x2154));  // PatientTelephoneNumbers
-    removals_.insert(DicomTag(0x0010, 0x2000));  // Medical Alerts
-
-    // Set the DeidentificationMethod tag
-    ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2008);
-  }
-  
-
-#if 0
-  /**
-   * This is a manual implementation by Alain Mazy. Only kept for reference.
-   * https://bitbucket.org/sjodogne/orthanc/commits/c6defdc4c611fca2ab528ba2c6937a742e0329a8?at=issue-46-anonymization
-   **/
-  
-  void DicomModification::SetupAnonymization2011()
-  {
-    // This is Table E.1-1 from PS 3.15-2011 - DICOM Part 15: Security and System Management Profiles
-    // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2011/11_15pu.pdf
-    
-    removals_.insert(DicomTag(0x0000, 0x1000));  // Affected SOP Instance UID
-    removals_.insert(DicomTag(0x0000, 0x1001));  // Requested SOP Instance UID
-    removals_.insert(DicomTag(0x0002, 0x0003));  // Media Storage SOP Instance UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances
-    removals_.insert(DicomTag(0x0004, 0x1511));  // Referenced SOP Instance UID in File
-    removals_.insert(DicomTag(0x0008, 0x0010));  // Irradiation Event UID
-    removals_.insert(DicomTag(0x0008, 0x0014));  // Instance Creator UID
-    //removals_.insert(DicomTag(0x0008, 0x0018));  // SOP Instance UID => set in Apply()
-    clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date
-    clearings_.insert(DicomTag(0x0008, 0x0021)); // Series Date
-    clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time
-    clearings_.insert(DicomTag(0x0008, 0x0031)); // Series Time
-    removals_.insert(DicomTag(0x0008, 0x0022));  // Acquisition Date
-    removals_.insert(DicomTag(0x0008, 0x0023));  // Content Date
-    removals_.insert(DicomTag(0x0008, 0x0024));  // Overlay Date
-    removals_.insert(DicomTag(0x0008, 0x0025));  // Curve Date
-    removals_.insert(DicomTag(0x0008, 0x002a));  // Acquisition DateTime
-    removals_.insert(DicomTag(0x0008, 0x0032));  // Acquisition Time
-    removals_.insert(DicomTag(0x0008, 0x0033));  // Content Time
-    removals_.insert(DicomTag(0x0008, 0x0034));  // Overlay Time
-    removals_.insert(DicomTag(0x0008, 0x0035));  // Curve Time
-    removals_.insert(DicomTag(0x0008, 0x0050));  // Accession Number
-    removals_.insert(DicomTag(0x0008, 0x0058));  // Failed SOP Instance UID List
-    removals_.insert(DicomTag(0x0008, 0x0080));  // Institution Name
-    removals_.insert(DicomTag(0x0008, 0x0081));  // Institution Address
-    removals_.insert(DicomTag(0x0008, 0x0082));  // Institution Code Sequence
-    removals_.insert(DicomTag(0x0008, 0x0090));  // Referring Physician's Name
-    removals_.insert(DicomTag(0x0008, 0x0092));  // Referring Physician's Address 
-    removals_.insert(DicomTag(0x0008, 0x0094));  // Referring Physician's Telephone Numbers 
-    removals_.insert(DicomTag(0x0008, 0x0096));  // Referring Physician's Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x010d));  // Context Group Extension Creator UID
-    removals_.insert(DicomTag(0x0008, 0x0201));  // Timezone Offset From UTC
-    removals_.insert(DicomTag(0x0008, 0x0300));  // Current Patient Location
-    removals_.insert(DicomTag(0x0008, 0x1010));  // Station Name
-    removals_.insert(DicomTag(0x0008, 0x1030));  // Study Description 
-    removals_.insert(DicomTag(0x0008, 0x103e));  // Series Description 
-    removals_.insert(DicomTag(0x0008, 0x1040));  // Institutional Department Name 
-    removals_.insert(DicomTag(0x0008, 0x1048));  // Physician(s) of Record 
-    removals_.insert(DicomTag(0x0008, 0x1049));  // Physician(s) of Record Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1050));  // Performing Physicians' Name
-    removals_.insert(DicomTag(0x0008, 0x1052));  // Performing Physicians Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1060));  // Name of Physician(s) Reading Study
-    removals_.insert(DicomTag(0x0008, 0x1062));  // Physician Reading Study Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1070));  // Operators' Name
-    removals_.insert(DicomTag(0x0008, 0x1072));  // Operators' Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1080));  // Admitting Diagnoses Description
-    removals_.insert(DicomTag(0x0008, 0x1084));  // Admitting Diagnoses Code Sequence
-    removals_.insert(DicomTag(0x0008, 0x1110));  // Referenced Study Sequence
-    removals_.insert(DicomTag(0x0008, 0x1111));  // Referenced Performed Procedure Step Sequence
-    removals_.insert(DicomTag(0x0008, 0x1120));  // Referenced Patient Sequence
-    removals_.insert(DicomTag(0x0008, 0x1140));  // Referenced Image Sequence
-    removals_.insert(DicomTag(0x0008, 0x1155));  // Referenced SOP Instance UID
-    removals_.insert(DicomTag(0x0008, 0x1195));  // Transaction UID
-    removals_.insert(DicomTag(0x0008, 0x2111));  // Derivation Description
-    removals_.insert(DicomTag(0x0008, 0x2112));  // Source Image Sequence
-    removals_.insert(DicomTag(0x0008, 0x4000));  // Identifying Comments
-    removals_.insert(DicomTag(0x0008, 0x9123));  // Creator Version UID
-    //removals_.insert(DicomTag(0x0010, 0x0010));  // Patient's Name => cf. below (*)
-    //removals_.insert(DicomTag(0x0010, 0x0020));  // Patient ID => cf. below (*)
-    removals_.insert(DicomTag(0x0010, 0x0030));  // Patient's Birth Date 
-    removals_.insert(DicomTag(0x0010, 0x0032));  // Patient's Birth Time 
-    clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex
-    removals_.insert(DicomTag(0x0010, 0x0050));  // Patient's Insurance Plan Code Sequence
-    removals_.insert(DicomTag(0x0010, 0x0101));  // Patient's Primary Language Code Sequence
-    removals_.insert(DicomTag(0x0010, 0x0102));  // Patient's Primary Language Modifier Code Sequence
-    removals_.insert(DicomTag(0x0010, 0x1000));  // Other Patient Ids
-    removals_.insert(DicomTag(0x0010, 0x1001));  // Other Patient Names 
-    removals_.insert(DicomTag(0x0010, 0x1002));  // Other Patient IDs Sequence
-    removals_.insert(DicomTag(0x0010, 0x1005));  // Patient's Birth Name
-    removals_.insert(DicomTag(0x0010, 0x1010));  // Patient's Age
-    removals_.insert(DicomTag(0x0010, 0x1020));  // Patient's Size 
-    removals_.insert(DicomTag(0x0010, 0x1030));  // Patient's Weight 
-    removals_.insert(DicomTag(0x0010, 0x1040));  // Patient's Address
-    removals_.insert(DicomTag(0x0010, 0x1050));  // Insurance Plan Identification
-    removals_.insert(DicomTag(0x0010, 0x1060));  // Patient's Mother's Birth Name
-    removals_.insert(DicomTag(0x0010, 0x1080));  // Military Rank
-    removals_.insert(DicomTag(0x0010, 0x1081));  // Branch of Service
-    removals_.insert(DicomTag(0x0010, 0x1090));  // Medical Record Locator
-    removals_.insert(DicomTag(0x0010, 0x2000));  // Medical Alerts
-    removals_.insert(DicomTag(0x0010, 0x2110));  // Allergies
-    removals_.insert(DicomTag(0x0010, 0x2150));  // Country of Residence
-    removals_.insert(DicomTag(0x0010, 0x2152));  // Region of Residence
-    removals_.insert(DicomTag(0x0010, 0x2154));  // PatientTelephoneNumbers
-    removals_.insert(DicomTag(0x0010, 0x2160));  // Ethnic Group
-    removals_.insert(DicomTag(0x0010, 0x2180));  // Occupation 
-    removals_.insert(DicomTag(0x0010, 0x21a0));  // Smoking Status
-    removals_.insert(DicomTag(0x0010, 0x21b0));  // Additional Patient's History
-    removals_.insert(DicomTag(0x0010, 0x21c0));  // Pregnancy Status
-    removals_.insert(DicomTag(0x0010, 0x21d0));  // Last Menstrual Date
-    removals_.insert(DicomTag(0x0010, 0x21f0));  // Patient's Religious Preference
-    removals_.insert(DicomTag(0x0010, 0x2203));  // Patient's Sex Neutered
-    removals_.insert(DicomTag(0x0010, 0x2297));  // Responsible Person
-    removals_.insert(DicomTag(0x0010, 0x2299));  // Responsible Organization
-    removals_.insert(DicomTag(0x0010, 0x4000));  // Patient Comments
-    removals_.insert(DicomTag(0x0018, 0x0010));  // Contrast Bolus Agent
-    removals_.insert(DicomTag(0x0018, 0x1000));  // Device Serial Number
-    removals_.insert(DicomTag(0x0018, 0x1002));  // Device UID
-    removals_.insert(DicomTag(0x0018, 0x1004));  // Plate ID
-    removals_.insert(DicomTag(0x0018, 0x1005));  // Generator ID
-    removals_.insert(DicomTag(0x0018, 0x1007));  // Cassette ID
-    removals_.insert(DicomTag(0x0018, 0x1008));  // Gantry ID
-    removals_.insert(DicomTag(0x0018, 0x1030));  // Protocol Name
-    removals_.insert(DicomTag(0x0018, 0x1400));  // Acquisition Device Processing Description
-    removals_.insert(DicomTag(0x0018, 0x4000));  // Acquisition Comments
-    removals_.insert(DicomTag(0x0018, 0x700a));  // Detector ID
-    removals_.insert(DicomTag(0x0018, 0xa003));  // Contribution Description
-    removals_.insert(DicomTag(0x0018, 0x9424));  // Acquisition Protocol Description
-    //removals_.insert(DicomTag(0x0020, 0x000d));  // Study Instance UID => set in Apply()
-    //removals_.insert(DicomTag(0x0020, 0x000e));  // Series Instance UID => set in Apply()
-    removals_.insert(DicomTag(0x0020, 0x0010));  // Study ID
-    removals_.insert(DicomTag(0x0020, 0x0052));  // Frame of Reference UID 
-    removals_.insert(DicomTag(0x0020, 0x0200));  // Synchronization Frame of Reference UID 
-    removals_.insert(DicomTag(0x0020, 0x3401));  // Modifying Device ID
-    removals_.insert(DicomTag(0x0020, 0x3404));  // Modifying Device Manufacturer
-    removals_.insert(DicomTag(0x0020, 0x3406));  // Modified Image Description
-    removals_.insert(DicomTag(0x0020, 0x4000));  // Image Comments
-    removals_.insert(DicomTag(0x0020, 0x9158));  // Frame Comments
-    removals_.insert(DicomTag(0x0020, 0x9161));  // Concatenation UID
-    removals_.insert(DicomTag(0x0020, 0x9164));  // Dimension Organization UID
-    //removals_.insert(DicomTag(0x0028, 0x1199));  // Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances
-    //removals_.insert(DicomTag(0x0028, 0x1214));  // Large Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances
-    removals_.insert(DicomTag(0x0028, 0x4000));  // Image Presentation Comments
-    removals_.insert(DicomTag(0x0032, 0x0012));  // Study ID Issuer
-    removals_.insert(DicomTag(0x0032, 0x1020));  // Scheduled Study Location
-    removals_.insert(DicomTag(0x0032, 0x1021));  // Scheduled Study Location AE Title
-    removals_.insert(DicomTag(0x0032, 0x1030));  // Reason for Study
-    removals_.insert(DicomTag(0x0032, 0x1032));  // Requesting Physician
-    removals_.insert(DicomTag(0x0032, 0x1033));  // Requesting Service
-    removals_.insert(DicomTag(0x0032, 0x1060));  // Requesting Procedure Description
-    removals_.insert(DicomTag(0x0032, 0x1070));  // Requested Contrast Agent
-    removals_.insert(DicomTag(0x0032, 0x4000));  // Study Comments
-    removals_.insert(DicomTag(0x0038, 0x0010));  // Admission ID
-    removals_.insert(DicomTag(0x0038, 0x0011));  // Issuer of Admission ID
-    removals_.insert(DicomTag(0x0038, 0x001e));  // Scheduled Patient Institution Residence
-    removals_.insert(DicomTag(0x0038, 0x0020));  // Admitting Date
-    removals_.insert(DicomTag(0x0038, 0x0021));  // Admitting Time
-    removals_.insert(DicomTag(0x0038, 0x0040));  // Discharge Diagnosis Description
-    removals_.insert(DicomTag(0x0038, 0x0050));  // Special Needs
-    removals_.insert(DicomTag(0x0038, 0x0060));  // Service Episode ID
-    removals_.insert(DicomTag(0x0038, 0x0061));  // Issuer of Service Episode ID
-    removals_.insert(DicomTag(0x0038, 0x0062));  // Service Episode Description
-    removals_.insert(DicomTag(0x0038, 0x0400));  // Patient's Institution Residence
-    removals_.insert(DicomTag(0x0038, 0x0500));  // Patient State
-    removals_.insert(DicomTag(0x0038, 0x4000));  // Visit Comments
-    removals_.insert(DicomTag(0x0038, 0x1234));  // Referenced Patient Alias Sequence
-    removals_.insert(DicomTag(0x0040, 0x0001));  // Scheduled Station AE Title
-    removals_.insert(DicomTag(0x0040, 0x0002));  // Scheduled Procedure Step Start Date
-    removals_.insert(DicomTag(0x0040, 0x0003));  // Scheduled Procedure Step Start Time
-    removals_.insert(DicomTag(0x0040, 0x0004));  // Scheduled Procedure Step End Date
-    removals_.insert(DicomTag(0x0040, 0x0005));  // Scheduled Procedure Step End Time
-    removals_.insert(DicomTag(0x0040, 0x0006));  // Scheduled Performing Physician Name
-    removals_.insert(DicomTag(0x0040, 0x0007));  // Scheduled Procedure Step Description
-    removals_.insert(DicomTag(0x0040, 0x000b));  // Scheduled Performing Physician Identification Sequence
-    removals_.insert(DicomTag(0x0040, 0x0010));  // Scheduled Station Name
-    removals_.insert(DicomTag(0x0040, 0x0011));  // Scheduled Procedure Step Location
-    removals_.insert(DicomTag(0x0040, 0x0012));  // Pre-Medication
-    removals_.insert(DicomTag(0x0040, 0x0241));  // Performed Station AE Title
-    removals_.insert(DicomTag(0x0040, 0x0242));  // Performed Station Name
-    removals_.insert(DicomTag(0x0040, 0x0243));  // Performed Location
-    removals_.insert(DicomTag(0x0040, 0x0244));  // Performed Procedure Step Start Date
-    removals_.insert(DicomTag(0x0040, 0x0245));  // Performed Procedure Step Start Time
-    removals_.insert(DicomTag(0x0040, 0x0248));  // Performed Station Name Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x0253));  // Performed Procedure Step ID
-    removals_.insert(DicomTag(0x0040, 0x0254));  // Performed Procedure Step Description
-    removals_.insert(DicomTag(0x0040, 0x0275));  // Request Attributes Sequence
-    removals_.insert(DicomTag(0x0040, 0x0280));  // Comments on Performed Procedure Step
-    removals_.insert(DicomTag(0x0040, 0x0555));  // Acquisition Context Sequence
-    removals_.insert(DicomTag(0x0040, 0x1001));  // Requested Procedure ID
-    removals_.insert(DicomTag(0x0040, 0x1010));  // Names of Intended Recipient of Results
-    removals_.insert(DicomTag(0x0040, 0x1011));  // Intended Recipient of Results Identification Sequence
-    removals_.insert(DicomTag(0x0040, 0x1004));  // Patient Transport Arrangements
-    removals_.insert(DicomTag(0x0040, 0x1005));  // Requested Procedure Location
-    removals_.insert(DicomTag(0x0040, 0x1101));  // Person Identification Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x1102));  // Person Address
-    removals_.insert(DicomTag(0x0040, 0x1103));  // Person Telephone Numbers
-    removals_.insert(DicomTag(0x0040, 0x1400));  // Requested Procedure Comments
-    removals_.insert(DicomTag(0x0040, 0x2001));  // Reason for Imaging Service Request
-    removals_.insert(DicomTag(0x0040, 0x2008));  // Order Entered By
-    removals_.insert(DicomTag(0x0040, 0x2009));  // Order Enterer Location
-    removals_.insert(DicomTag(0x0040, 0x2010));  // Order Callback Phone Number
-    removals_.insert(DicomTag(0x0040, 0x2016));  // Placer Order Number of Imaging Service Request
-    removals_.insert(DicomTag(0x0040, 0x2017));  // Filler Order Number of Imaging Service Request
-    removals_.insert(DicomTag(0x0040, 0x2400));  // Imaging Service Request Comments
-    removals_.insert(DicomTag(0x0040, 0x4023));  // Referenced General Purpose Scheduled Procedure Step Transaction UID
-    removals_.insert(DicomTag(0x0040, 0x4025));  // Scheduled Station Name Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x4027));  // Scheduled Station Geographic Location Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x4030));  // Performed Station Geographic Location Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x4034));  // Scheduled Human Performers Sequence
-    removals_.insert(DicomTag(0x0040, 0x4035));  // Actual Human Performers Sequence
-    removals_.insert(DicomTag(0x0040, 0x4036));  // Human Performers Organization
-    removals_.insert(DicomTag(0x0040, 0x4037));  // Human Performers Name
-    removals_.insert(DicomTag(0x0040, 0xa027));  // Verifying Organization
-    removals_.insert(DicomTag(0x0040, 0xa073));  // Verifying Observer Sequence
-    removals_.insert(DicomTag(0x0040, 0xa075));  // Verifying Observer Name
-    removals_.insert(DicomTag(0x0040, 0xa078));  // Author Observer Sequence
-    removals_.insert(DicomTag(0x0040, 0xa07a));  // Participant Sequence
-    removals_.insert(DicomTag(0x0040, 0xa07c));  // Custodial Organization Sequence
-    removals_.insert(DicomTag(0x0040, 0xa088));  // Verifying Observer Identification Code Sequence
-    removals_.insert(DicomTag(0x0040, 0xa123));  // Person Name
-    removals_.insert(DicomTag(0x0040, 0xa124));  // UID
-    removals_.insert(DicomTag(0x0040, 0xa730));  // Content Sequence 
-    removals_.insert(DicomTag(0x0040, 0x3001));  // Confidentiality Constraint on Patient Data Description
-    removals_.insert(DicomTag(0x0040, 0xdb0c));  // Template Extension Organization UID
-    removals_.insert(DicomTag(0x0040, 0xdb0d));  // Template Extension Creator UID
-    removals_.insert(DicomTag(0x0070, 0x0001));  // Graphic Annotation Sequence
-    removals_.insert(DicomTag(0x0070, 0x0084));  // Content Creator's Name
-    removals_.insert(DicomTag(0x0070, 0x0086));  // Content Creator's Identification Code Sequence
-    removals_.insert(DicomTag(0x0070, 0x031a));  // Fiducial UID
-    removals_.insert(DicomTag(0x0088, 0x0140));  // Storage Media File-set UID
-    removals_.insert(DicomTag(0x0088, 0x0200));  // Icon Image Sequence
-    removals_.insert(DicomTag(0x0088, 0x0904));  // Topic Title
-    removals_.insert(DicomTag(0x0088, 0x0906));  // Topic Subject
-    removals_.insert(DicomTag(0x0088, 0x0910));  // Topic Author
-    removals_.insert(DicomTag(0x0088, 0x0912));  // Topic Key Words
-    removals_.insert(DicomTag(0x0400, 0x0100));  // Digital Signature UID
-    removals_.insert(DicomTag(0x0400, 0x0402));  // Referenced Digital Signature Sequence
-    removals_.insert(DicomTag(0x0400, 0x0403));  // Referenced SOP Instance MAC Sequence
-    removals_.insert(DicomTag(0x0400, 0x0404));  // MAC
-    removals_.insert(DicomTag(0x0400, 0x0550));  // Modified Attributes Sequence
-    removals_.insert(DicomTag(0x0400, 0x0561));  // Original Attributes Sequence
-    removals_.insert(DicomTag(0x2030, 0x0020));  // Text String
-    removals_.insert(DicomTag(0x3006, 0x0024));  // Referenced Frame of Reference UID
-    removals_.insert(DicomTag(0x3006, 0x00c2));  // Related Frame of Reference UID 
-    removals_.insert(DicomTag(0x300a, 0x0013));  // Dose Reference UID
-    removals_.insert(DicomTag(0x300e, 0x0008));  // Reviewer Name
-    removals_.insert(DicomTag(0x4000, 0x0010));  // Arbitrary
-    removals_.insert(DicomTag(0x4000, 0x4000));  // Text Comments
-    removals_.insert(DicomTag(0x4008, 0x0042));  // Results ID Issuer
-    removals_.insert(DicomTag(0x4008, 0x0102));  // Interpretation Recorder
-    removals_.insert(DicomTag(0x4008, 0x010a));  // Interpretation Transcriber
-    removals_.insert(DicomTag(0x4008, 0x010b));  // Interpretation Text
-    removals_.insert(DicomTag(0x4008, 0x010c));  // Interpretation Author
-    removals_.insert(DicomTag(0x4008, 0x0111));  // Interpretation Approver Sequence
-    removals_.insert(DicomTag(0x4008, 0x0114));  // Physician Approving Interpretation
-    removals_.insert(DicomTag(0x4008, 0x0115));  // Interpretation Diagnosis Description
-    removals_.insert(DicomTag(0x4008, 0x0118));  // Results Distribution List Sequence
-    removals_.insert(DicomTag(0x4008, 0x0119));  // Distribution Name
-    removals_.insert(DicomTag(0x4008, 0x011a));  // Distribution Address
-    removals_.insert(DicomTag(0x4008, 0x0202));  // Interpretation ID Issuer
-    removals_.insert(DicomTag(0x4008, 0x0300));  // Impressions
-    removals_.insert(DicomTag(0x4008, 0x4000));  // Results Comments
-    removals_.insert(DicomTag(0xfffa, 0xfffa));  // Digital Signature Sequence
-    removals_.insert(DicomTag(0xfffc, 0xfffc));  // Data Set Trailing Padding
-    //removals_.insert(DicomTag(0x60xx, 0x4000));  // Overlay Comments => TODO
-    //removals_.insert(DicomTag(0x60xx, 0x3000));  // Overlay Data => TODO
-
-    // Set the DeidentificationMethod tag
-    ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2011);
-  }
-#endif
-  
-  
-
-  void DicomModification::SetupAnonymization2017c()
-  {
-    /**
-     * This is Table E.1-1 from PS 3.15-2017c (DICOM Part 15: Security
-     * and System Management Profiles), "basic profile" column. It was
-     * generated automatically with the
-     * "../Resources/GenerateAnonymizationProfile.py" script.
-     * https://raw.githubusercontent.com/jodogne/dicom-specification/master/2017c/part15.pdf
-     **/
-    
-    // TODO: (50xx,xxxx) with rule X                                 // Curve Data
-    // TODO: (60xx,3000) with rule X                                 // Overlay Data
-    // TODO: (60xx,4000) with rule X                                 // Overlay Comments
-    // Tag (0x0008, 0x0018) is set in Apply()                        // SOP Instance UID
-    // Tag (0x0010, 0x0010) is set below (*)                         // Patient's Name
-    // Tag (0x0010, 0x0020) is set below (*)                         // Patient ID
-    // Tag (0x0020, 0x000d) is set in Apply()                        // Study Instance UID
-    // Tag (0x0020, 0x000e) is set in Apply()                        // Series Instance UID
-    clearings_.insert(DicomTag(0x0008, 0x0020));                     // Study Date
-    clearings_.insert(DicomTag(0x0008, 0x0023));  /* Z/D */          // Content Date
-    clearings_.insert(DicomTag(0x0008, 0x0030));                     // Study Time
-    clearings_.insert(DicomTag(0x0008, 0x0033));  /* Z/D */          // Content Time
-    clearings_.insert(DicomTag(0x0008, 0x0050));                     // Accession Number
-    clearings_.insert(DicomTag(0x0008, 0x0090));                     // Referring Physician's Name
-    clearings_.insert(DicomTag(0x0008, 0x009c));                     // Consulting Physician's Name
-    clearings_.insert(DicomTag(0x0010, 0x0030));                     // Patient's Birth Date
-    clearings_.insert(DicomTag(0x0010, 0x0040));                     // Patient's Sex
-    clearings_.insert(DicomTag(0x0018, 0x0010));  /* Z/D */          // Contrast Bolus Agent
-    clearings_.insert(DicomTag(0x0020, 0x0010));                     // Study ID
-    clearings_.insert(DicomTag(0x0040, 0x1101));  /* D */            // Person Identification Code Sequence
-    clearings_.insert(DicomTag(0x0040, 0x2016));                     // Placer Order Number / Imaging Service Request
-    clearings_.insert(DicomTag(0x0040, 0x2017));                     // Filler Order Number / Imaging Service Request
-    clearings_.insert(DicomTag(0x0040, 0xa073));  /* D */            // Verifying Observer Sequence
-    clearings_.insert(DicomTag(0x0040, 0xa075));  /* D */            // Verifying Observer Name
-    clearings_.insert(DicomTag(0x0040, 0xa088));                     // Verifying Observer Identification Code Sequence
-    clearings_.insert(DicomTag(0x0040, 0xa123));  /* D */            // Person Name
-    clearings_.insert(DicomTag(0x0070, 0x0001));  /* D */            // Graphic Annotation Sequence
-    clearings_.insert(DicomTag(0x0070, 0x0084));                     // Content Creator's Name
-    removals_.insert(DicomTag(0x0000, 0x1000));                      // Affected SOP Instance UID
-    removals_.insert(DicomTag(0x0000, 0x1001));   /* TODO UID */     // Requested SOP Instance UID
-    removals_.insert(DicomTag(0x0002, 0x0003));   /* TODO UID */     // Media Storage SOP Instance UID
-    removals_.insert(DicomTag(0x0004, 0x1511));   /* TODO UID */     // Referenced SOP Instance UID in File
-    removals_.insert(DicomTag(0x0008, 0x0014));   /* TODO UID */     // Instance Creator UID
-    removals_.insert(DicomTag(0x0008, 0x0015));                      // Instance Coercion DateTime
-    removals_.insert(DicomTag(0x0008, 0x0021));   /* X/D */          // Series Date
-    removals_.insert(DicomTag(0x0008, 0x0022));   /* X/Z */          // Acquisition Date
-    removals_.insert(DicomTag(0x0008, 0x0024));                      // Overlay Date
-    removals_.insert(DicomTag(0x0008, 0x0025));                      // Curve Date
-    removals_.insert(DicomTag(0x0008, 0x002a));   /* X/D */          // Acquisition DateTime
-    removals_.insert(DicomTag(0x0008, 0x0031));   /* X/D */          // Series Time
-    removals_.insert(DicomTag(0x0008, 0x0032));   /* X/Z */          // Acquisition Time
-    removals_.insert(DicomTag(0x0008, 0x0034));                      // Overlay Time
-    removals_.insert(DicomTag(0x0008, 0x0035));                      // Curve Time
-    removals_.insert(DicomTag(0x0008, 0x0058));   /* TODO UID */     // Failed SOP Instance UID List
-    removals_.insert(DicomTag(0x0008, 0x0080));   /* X/Z/D */        // Institution Name
-    removals_.insert(DicomTag(0x0008, 0x0081));                      // Institution Address
-    removals_.insert(DicomTag(0x0008, 0x0082));   /* X/Z/D */        // Institution Code Sequence
-    removals_.insert(DicomTag(0x0008, 0x0092));                      // Referring Physician's Address
-    removals_.insert(DicomTag(0x0008, 0x0094));                      // Referring Physician's Telephone Numbers
-    removals_.insert(DicomTag(0x0008, 0x0096));                      // Referring Physician Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x009d));                      // Consulting Physician Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x0201));                      // Timezone Offset From UTC
-    removals_.insert(DicomTag(0x0008, 0x1010));   /* X/Z/D */        // Station Name
-    removals_.insert(DicomTag(0x0008, 0x1030));                      // Study Description
-    removals_.insert(DicomTag(0x0008, 0x103e));                      // Series Description
-    removals_.insert(DicomTag(0x0008, 0x1040));                      // Institutional Department Name
-    removals_.insert(DicomTag(0x0008, 0x1048));                      // Physician(s) of Record
-    removals_.insert(DicomTag(0x0008, 0x1049));                      // Physician(s) of Record Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1050));                      // Performing Physicians' Name
-    removals_.insert(DicomTag(0x0008, 0x1052));                      // Performing Physician Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1060));                      // Name of Physician(s) Reading Study
-    removals_.insert(DicomTag(0x0008, 0x1062));                      // Physician(s) Reading Study Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1070));   /* X/Z/D */        // Operators' Name
-    removals_.insert(DicomTag(0x0008, 0x1072));   /* X/D */          // Operators' Identification Sequence
-    removals_.insert(DicomTag(0x0008, 0x1080));                      // Admitting Diagnoses Description
-    removals_.insert(DicomTag(0x0008, 0x1084));                      // Admitting Diagnoses Code Sequence
-    removals_.insert(DicomTag(0x0008, 0x1110));   /* X/Z */          // Referenced Study Sequence
-    removals_.insert(DicomTag(0x0008, 0x1111));   /* X/Z/D */        // Referenced Performed Procedure Step Sequence
-    removals_.insert(DicomTag(0x0008, 0x1120));                      // Referenced Patient Sequence
-    removals_.insert(DicomTag(0x0008, 0x1140));   /* X/Z/U* */       // Referenced Image Sequence
-    removals_.insert(DicomTag(0x0008, 0x1155));   /* TODO UID */     // Referenced SOP Instance UID
-    removals_.insert(DicomTag(0x0008, 0x1195));   /* TODO UID */     // Transaction UID
-    removals_.insert(DicomTag(0x0008, 0x2111));                      // Derivation Description
-    removals_.insert(DicomTag(0x0008, 0x2112));   /* X/Z/U* */       // Source Image Sequence
-    removals_.insert(DicomTag(0x0008, 0x3010));   /* TODO UID */     // Irradiation Event UID
-    removals_.insert(DicomTag(0x0008, 0x4000));                      // Identifying Comments
-    removals_.insert(DicomTag(0x0010, 0x0021));                      // Issuer of Patient ID
-    removals_.insert(DicomTag(0x0010, 0x0032));                      // Patient's Birth Time
-    removals_.insert(DicomTag(0x0010, 0x0050));                      // Patient's Insurance Plan Code Sequence
-    removals_.insert(DicomTag(0x0010, 0x0101));                      // Patient's Primary Language Code Sequence
-    removals_.insert(DicomTag(0x0010, 0x0102));                      // Patient's Primary Language Modifier Code Sequence
-    removals_.insert(DicomTag(0x0010, 0x1000));                      // Other Patient IDs
-    removals_.insert(DicomTag(0x0010, 0x1001));                      // Other Patient Names
-    removals_.insert(DicomTag(0x0010, 0x1002));                      // Other Patient IDs Sequence
-    removals_.insert(DicomTag(0x0010, 0x1005));                      // Patient's Birth Name
-    removals_.insert(DicomTag(0x0010, 0x1010));                      // Patient's Age
-    removals_.insert(DicomTag(0x0010, 0x1020));                      // Patient's Size
-    removals_.insert(DicomTag(0x0010, 0x1030));                      // Patient's Weight
-    removals_.insert(DicomTag(0x0010, 0x1040));                      // Patient Address
-    removals_.insert(DicomTag(0x0010, 0x1050));                      // Insurance Plan Identification
-    removals_.insert(DicomTag(0x0010, 0x1060));                      // Patient's Mother's Birth Name
-    removals_.insert(DicomTag(0x0010, 0x1080));                      // Military Rank
-    removals_.insert(DicomTag(0x0010, 0x1081));                      // Branch of Service
-    removals_.insert(DicomTag(0x0010, 0x1090));                      // Medical Record Locator
-    removals_.insert(DicomTag(0x0010, 0x1100));                      // Referenced Patient Photo Sequence
-    removals_.insert(DicomTag(0x0010, 0x2000));                      // Medical Alerts
-    removals_.insert(DicomTag(0x0010, 0x2110));                      // Allergies
-    removals_.insert(DicomTag(0x0010, 0x2150));                      // Country of Residence
-    removals_.insert(DicomTag(0x0010, 0x2152));                      // Region of Residence
-    removals_.insert(DicomTag(0x0010, 0x2154));                      // Patient's Telephone Numbers
-    removals_.insert(DicomTag(0x0010, 0x2155));                      // Patient's Telecom Information
-    removals_.insert(DicomTag(0x0010, 0x2160));                      // Ethnic Group
-    removals_.insert(DicomTag(0x0010, 0x2180));                      // Occupation
-    removals_.insert(DicomTag(0x0010, 0x21a0));                      // Smoking Status
-    removals_.insert(DicomTag(0x0010, 0x21b0));                      // Additional Patient's History
-    removals_.insert(DicomTag(0x0010, 0x21c0));                      // Pregnancy Status
-    removals_.insert(DicomTag(0x0010, 0x21d0));                      // Last Menstrual Date
-    removals_.insert(DicomTag(0x0010, 0x21f0));                      // Patient's Religious Preference
-    removals_.insert(DicomTag(0x0010, 0x2203));   /* X/Z */          // Patient Sex Neutered
-    removals_.insert(DicomTag(0x0010, 0x2297));                      // Responsible Person
-    removals_.insert(DicomTag(0x0010, 0x2299));                      // Responsible Organization
-    removals_.insert(DicomTag(0x0010, 0x4000));                      // Patient Comments
-    removals_.insert(DicomTag(0x0018, 0x1000));   /* X/Z/D */        // Device Serial Number
-    removals_.insert(DicomTag(0x0018, 0x1002));   /* TODO UID */     // Device UID
-    removals_.insert(DicomTag(0x0018, 0x1004));                      // Plate ID
-    removals_.insert(DicomTag(0x0018, 0x1005));                      // Generator ID
-    removals_.insert(DicomTag(0x0018, 0x1007));                      // Cassette ID
-    removals_.insert(DicomTag(0x0018, 0x1008));                      // Gantry ID
-    removals_.insert(DicomTag(0x0018, 0x1030));   /* X/D */          // Protocol Name
-    removals_.insert(DicomTag(0x0018, 0x1400));   /* X/D */          // Acquisition Device Processing Description
-    removals_.insert(DicomTag(0x0018, 0x2042));   /* TODO UID */     // Target UID
-    removals_.insert(DicomTag(0x0018, 0x4000));                      // Acquisition Comments
-    removals_.insert(DicomTag(0x0018, 0x700a));   /* X/D */          // Detector ID
-    removals_.insert(DicomTag(0x0018, 0x9424));                      // Acquisition Protocol Description
-    removals_.insert(DicomTag(0x0018, 0x9516));   /* X/D */          // Start Acquisition DateTime
-    removals_.insert(DicomTag(0x0018, 0x9517));   /* X/D */          // End Acquisition DateTime
-    removals_.insert(DicomTag(0x0018, 0xa003));                      // Contribution Description
-    removals_.insert(DicomTag(0x0020, 0x0052));   /* TODO UID */     // Frame of Reference UID
-    removals_.insert(DicomTag(0x0020, 0x0200));   /* TODO UID */     // Synchronization Frame of Reference UID
-    removals_.insert(DicomTag(0x0020, 0x3401));                      // Modifying Device ID
-    removals_.insert(DicomTag(0x0020, 0x3404));                      // Modifying Device Manufacturer
-    removals_.insert(DicomTag(0x0020, 0x3406));                      // Modified Image Description
-    removals_.insert(DicomTag(0x0020, 0x4000));                      // Image Comments
-    removals_.insert(DicomTag(0x0020, 0x9158));                      // Frame Comments
-    removals_.insert(DicomTag(0x0020, 0x9161));   /* TODO UID */     // Concatenation UID
-    removals_.insert(DicomTag(0x0020, 0x9164));   /* TODO UID */     // Dimension Organization UID
-    removals_.insert(DicomTag(0x0028, 0x1199));   /* TODO UID */     // Palette Color Lookup Table UID
-    removals_.insert(DicomTag(0x0028, 0x1214));   /* TODO UID */     // Large Palette Color Lookup Table UID
-    removals_.insert(DicomTag(0x0028, 0x4000));                      // Image Presentation Comments
-    removals_.insert(DicomTag(0x0032, 0x0012));                      // Study ID Issuer
-    removals_.insert(DicomTag(0x0032, 0x1020));                      // Scheduled Study Location
-    removals_.insert(DicomTag(0x0032, 0x1021));                      // Scheduled Study Location AE Title
-    removals_.insert(DicomTag(0x0032, 0x1030));                      // Reason for Study
-    removals_.insert(DicomTag(0x0032, 0x1032));                      // Requesting Physician
-    removals_.insert(DicomTag(0x0032, 0x1033));                      // Requesting Service
-    removals_.insert(DicomTag(0x0032, 0x1060));   /* X/Z */          // Requested Procedure Description
-    removals_.insert(DicomTag(0x0032, 0x1070));                      // Requested Contrast Agent
-    removals_.insert(DicomTag(0x0032, 0x4000));                      // Study Comments
-    removals_.insert(DicomTag(0x0038, 0x0004));                      // Referenced Patient Alias Sequence
-    removals_.insert(DicomTag(0x0038, 0x0010));                      // Admission ID
-    removals_.insert(DicomTag(0x0038, 0x0011));                      // Issuer of Admission ID
-    removals_.insert(DicomTag(0x0038, 0x001e));                      // Scheduled Patient Institution Residence
-    removals_.insert(DicomTag(0x0038, 0x0020));                      // Admitting Date
-    removals_.insert(DicomTag(0x0038, 0x0021));                      // Admitting Time
-    removals_.insert(DicomTag(0x0038, 0x0040));                      // Discharge Diagnosis Description
-    removals_.insert(DicomTag(0x0038, 0x0050));                      // Special Needs
-    removals_.insert(DicomTag(0x0038, 0x0060));                      // Service Episode ID
-    removals_.insert(DicomTag(0x0038, 0x0061));                      // Issuer of Service Episode ID
-    removals_.insert(DicomTag(0x0038, 0x0062));                      // Service Episode Description
-    removals_.insert(DicomTag(0x0038, 0x0300));                      // Current Patient Location
-    removals_.insert(DicomTag(0x0038, 0x0400));                      // Patient's Institution Residence
-    removals_.insert(DicomTag(0x0038, 0x0500));                      // Patient State
-    removals_.insert(DicomTag(0x0038, 0x4000));                      // Visit Comments
-    removals_.insert(DicomTag(0x0040, 0x0001));                      // Scheduled Station AE Title
-    removals_.insert(DicomTag(0x0040, 0x0002));                      // Scheduled Procedure Step Start Date
-    removals_.insert(DicomTag(0x0040, 0x0003));                      // Scheduled Procedure Step Start Time
-    removals_.insert(DicomTag(0x0040, 0x0004));                      // Scheduled Procedure Step End Date
-    removals_.insert(DicomTag(0x0040, 0x0005));                      // Scheduled Procedure Step End Time
-    removals_.insert(DicomTag(0x0040, 0x0006));                      // Scheduled Performing Physician Name
-    removals_.insert(DicomTag(0x0040, 0x0007));                      // Scheduled Procedure Step Description
-    removals_.insert(DicomTag(0x0040, 0x000b));                      // Scheduled Performing Physician Identification Sequence
-    removals_.insert(DicomTag(0x0040, 0x0010));                      // Scheduled Station Name
-    removals_.insert(DicomTag(0x0040, 0x0011));                      // Scheduled Procedure Step Location
-    removals_.insert(DicomTag(0x0040, 0x0012));                      // Pre-Medication
-    removals_.insert(DicomTag(0x0040, 0x0241));                      // Performed Station AE Title
-    removals_.insert(DicomTag(0x0040, 0x0242));                      // Performed Station Name
-    removals_.insert(DicomTag(0x0040, 0x0243));                      // Performed Location
-    removals_.insert(DicomTag(0x0040, 0x0244));                      // Performed Procedure Step Start Date
-    removals_.insert(DicomTag(0x0040, 0x0245));                      // Performed Procedure Step Start Time
-    removals_.insert(DicomTag(0x0040, 0x0250));                      // Performed Procedure Step End Date
-    removals_.insert(DicomTag(0x0040, 0x0251));                      // Performed Procedure Step End Time
-    removals_.insert(DicomTag(0x0040, 0x0253));                      // Performed Procedure Step ID
-    removals_.insert(DicomTag(0x0040, 0x0254));                      // Performed Procedure Step Description
-    removals_.insert(DicomTag(0x0040, 0x0275));                      // Request Attributes Sequence
-    removals_.insert(DicomTag(0x0040, 0x0280));                      // Comments on the Performed Procedure Step
-    removals_.insert(DicomTag(0x0040, 0x0555));                      // Acquisition Context Sequence
-    removals_.insert(DicomTag(0x0040, 0x1001));                      // Requested Procedure ID
-    removals_.insert(DicomTag(0x0040, 0x1004));                      // Patient Transport Arrangements
-    removals_.insert(DicomTag(0x0040, 0x1005));                      // Requested Procedure Location
-    removals_.insert(DicomTag(0x0040, 0x1010));                      // Names of Intended Recipient of Results
-    removals_.insert(DicomTag(0x0040, 0x1011));                      // Intended Recipients of Results Identification Sequence
-    removals_.insert(DicomTag(0x0040, 0x1102));                      // Person Address
-    removals_.insert(DicomTag(0x0040, 0x1103));                      // Person's Telephone Numbers
-    removals_.insert(DicomTag(0x0040, 0x1104));                      // Person's Telecom Information
-    removals_.insert(DicomTag(0x0040, 0x1400));                      // Requested Procedure Comments
-    removals_.insert(DicomTag(0x0040, 0x2001));                      // Reason for the Imaging Service Request
-    removals_.insert(DicomTag(0x0040, 0x2008));                      // Order Entered By
-    removals_.insert(DicomTag(0x0040, 0x2009));                      // Order Enterer Location
-    removals_.insert(DicomTag(0x0040, 0x2010));                      // Order Callback Phone Number
-    removals_.insert(DicomTag(0x0040, 0x2011));                      // Order Callback Telecom Information
-    removals_.insert(DicomTag(0x0040, 0x2400));                      // Imaging Service Request Comments
-    removals_.insert(DicomTag(0x0040, 0x3001));                      // Confidentiality Constraint on Patient Data Description
-    removals_.insert(DicomTag(0x0040, 0x4005));                      // Scheduled Procedure Step Start DateTime
-    removals_.insert(DicomTag(0x0040, 0x4010));                      // Scheduled Procedure Step Modification DateTime
-    removals_.insert(DicomTag(0x0040, 0x4011));                      // Expected Completion DateTime
-    removals_.insert(DicomTag(0x0040, 0x4023));   /* TODO UID */     // Referenced General Purpose Scheduled Procedure Step Transaction UID
-    removals_.insert(DicomTag(0x0040, 0x4025));                      // Scheduled Station Name Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x4027));                      // Scheduled Station Geographic Location Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x4028));                      // Performed Station Name Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x4030));                      // Performed Station Geographic Location Code Sequence
-    removals_.insert(DicomTag(0x0040, 0x4034));                      // Scheduled Human Performers Sequence
-    removals_.insert(DicomTag(0x0040, 0x4035));                      // Actual Human Performers Sequence
-    removals_.insert(DicomTag(0x0040, 0x4036));                      // Human Performers Organization
-    removals_.insert(DicomTag(0x0040, 0x4037));                      // Human Performers Name
-    removals_.insert(DicomTag(0x0040, 0x4050));                      // Performed Procedure Step Start DateTime
-    removals_.insert(DicomTag(0x0040, 0x4051));                      // Performed Procedure Step End DateTime
-    removals_.insert(DicomTag(0x0040, 0x4052));                      // Procedure Step Cancellation DateTime
-    removals_.insert(DicomTag(0x0040, 0xa027));                      // Verifying Organization
-    removals_.insert(DicomTag(0x0040, 0xa078));                      // Author Observer Sequence
-    removals_.insert(DicomTag(0x0040, 0xa07a));                      // Participant Sequence
-    removals_.insert(DicomTag(0x0040, 0xa07c));                      // Custodial Organization Sequence
-    removals_.insert(DicomTag(0x0040, 0xa124));   /* TODO UID */     // UID
-    removals_.insert(DicomTag(0x0040, 0xa171));   /* TODO UID */     // Observation UID
-    removals_.insert(DicomTag(0x0040, 0xa172));   /* TODO UID */     // Referenced Observation UID (Trial)
-    removals_.insert(DicomTag(0x0040, 0xa192));                      // Observation Date (Trial)
-    removals_.insert(DicomTag(0x0040, 0xa193));                      // Observation Time (Trial)
-    removals_.insert(DicomTag(0x0040, 0xa307));                      // Current Observer (Trial)
-    removals_.insert(DicomTag(0x0040, 0xa352));                      // Verbal Source (Trial)
-    removals_.insert(DicomTag(0x0040, 0xa353));                      // Address (Trial)
-    removals_.insert(DicomTag(0x0040, 0xa354));                      // Telephone Number (Trial)
-    removals_.insert(DicomTag(0x0040, 0xa358));                      // Verbal Source Identifier Code Sequence (Trial)
-    removals_.insert(DicomTag(0x0040, 0xa402));   /* TODO UID */     // Observation Subject UID (Trial)
-    removals_.insert(DicomTag(0x0040, 0xa730));                      // Content Sequence
-    removals_.insert(DicomTag(0x0040, 0xdb0c));   /* TODO UID */     // Template Extension Organization UID
-    removals_.insert(DicomTag(0x0040, 0xdb0d));   /* TODO UID */     // Template Extension Creator UID
-    removals_.insert(DicomTag(0x0062, 0x0021));   /* TODO UID */     // Tracking UID
-    removals_.insert(DicomTag(0x0070, 0x0086));                      // Content Creator's Identification Code Sequence
-    removals_.insert(DicomTag(0x0070, 0x031a));   /* TODO UID */     // Fiducial UID
-    removals_.insert(DicomTag(0x0070, 0x1101));   /* TODO UID */     // Presentation Display Collection UID
-    removals_.insert(DicomTag(0x0070, 0x1102));   /* TODO UID */     // Presentation Sequence Collection UID
-    removals_.insert(DicomTag(0x0088, 0x0140));   /* TODO UID */     // Storage Media File-set UID
-    removals_.insert(DicomTag(0x0088, 0x0200));                      // Icon Image Sequence(see Note 12)
-    removals_.insert(DicomTag(0x0088, 0x0904));                      // Topic Title
-    removals_.insert(DicomTag(0x0088, 0x0906));                      // Topic Subject
-    removals_.insert(DicomTag(0x0088, 0x0910));                      // Topic Author
-    removals_.insert(DicomTag(0x0088, 0x0912));                      // Topic Keywords
-    removals_.insert(DicomTag(0x0400, 0x0100));                      // Digital Signature UID
-    removals_.insert(DicomTag(0x0400, 0x0402));                      // Referenced Digital Signature Sequence
-    removals_.insert(DicomTag(0x0400, 0x0403));                      // Referenced SOP Instance MAC Sequence
-    removals_.insert(DicomTag(0x0400, 0x0404));                      // MAC
-    removals_.insert(DicomTag(0x0400, 0x0550));                      // Modified Attributes Sequence
-    removals_.insert(DicomTag(0x0400, 0x0561));                      // Original Attributes Sequence
-    removals_.insert(DicomTag(0x2030, 0x0020));                      // Text String
-    removals_.insert(DicomTag(0x3006, 0x0024));   /* TODO UID */     // Referenced Frame of Reference UID
-    removals_.insert(DicomTag(0x3006, 0x00c2));   /* TODO UID */     // Related Frame of Reference UID
-    removals_.insert(DicomTag(0x3008, 0x0105));                      // Source Serial Number
-    removals_.insert(DicomTag(0x300a, 0x0013));   /* TODO UID */     // Dose Reference UID
-    removals_.insert(DicomTag(0x300c, 0x0113));                      // Reason for Omission Description
-    removals_.insert(DicomTag(0x300e, 0x0008));   /* X/Z */          // Reviewer Name
-    removals_.insert(DicomTag(0x4000, 0x0010));                      // Arbitrary
-    removals_.insert(DicomTag(0x4000, 0x4000));                      // Text Comments
-    removals_.insert(DicomTag(0x4008, 0x0042));                      // Results ID Issuer
-    removals_.insert(DicomTag(0x4008, 0x0102));                      // Interpretation Recorder
-    removals_.insert(DicomTag(0x4008, 0x010a));                      // Interpretation Transcriber
-    removals_.insert(DicomTag(0x4008, 0x010b));                      // Interpretation Text
-    removals_.insert(DicomTag(0x4008, 0x010c));                      // Interpretation Author
-    removals_.insert(DicomTag(0x4008, 0x0111));                      // Interpretation Approver Sequence
-    removals_.insert(DicomTag(0x4008, 0x0114));                      // Physician Approving Interpretation
-    removals_.insert(DicomTag(0x4008, 0x0115));                      // Interpretation Diagnosis Description
-    removals_.insert(DicomTag(0x4008, 0x0118));                      // Results Distribution List Sequence
-    removals_.insert(DicomTag(0x4008, 0x0119));                      // Distribution Name
-    removals_.insert(DicomTag(0x4008, 0x011a));                      // Distribution Address
-    removals_.insert(DicomTag(0x4008, 0x0202));                      // Interpretation ID Issuer
-    removals_.insert(DicomTag(0x4008, 0x0300));                      // Impressions
-    removals_.insert(DicomTag(0x4008, 0x4000));                      // Results Comments
-    removals_.insert(DicomTag(0xfffa, 0xfffa));                      // Digital Signatures Sequence
-    removals_.insert(DicomTag(0xfffc, 0xfffc));                      // Data Set Trailing Padding
-    
-    // Set the DeidentificationMethod tag
-    ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2017c);
-  }
-  
-
-  void DicomModification::SetupAnonymization(DicomVersion version)
-  {
-    removals_.clear();
-    clearings_.clear();
-    ClearReplacements();
-    removePrivateTags_ = true;
-    level_ = ResourceType_Patient;
-    uidMap_.clear();
-    privateTagsToKeep_.clear();
-
-    switch (version)
-    {
-      case DicomVersion_2008:
-        SetupAnonymization2008();
-        break;
-
-      case DicomVersion_2017c:
-        SetupAnonymization2017c();
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    // Set the PatientIdentityRemoved tag
-    ReplaceInternal(DicomTag(0x0012, 0x0062), "YES");
-
-    // (*) Choose a random patient name and ID
-    std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient);
-    ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId);
-    ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId);
-  }
-
-  void DicomModification::Apply(ParsedDicomFile& toModify)
-  {
-    // Check the request
-    assert(ResourceType_Patient + 1 == ResourceType_Study &&
-           ResourceType_Study + 1 == ResourceType_Series &&
-           ResourceType_Series + 1 == ResourceType_Instance);
-
-    if (IsRemoved(DICOM_TAG_PATIENT_ID) ||
-        IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) ||
-        IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) ||
-        IsRemoved(DICOM_TAG_SOP_INSTANCE_UID))
-    {
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-    
-
-    // Sanity checks at the patient level
-    if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID))
-    {
-      LOG(ERROR) << "When modifying a patient, her PatientID is required to be modified";
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-
-    if (!allowManualIdentifiers_)
-    {
-      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
-      {
-        LOG(ERROR) << "When modifying a patient, the StudyInstanceUID cannot be manually modified";
-        throw OrthancException(ErrorCode_BadRequest);
-      }
-
-      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
-      {
-        LOG(ERROR) << "When modifying a patient, the SeriesInstanceUID cannot be manually modified";
-        throw OrthancException(ErrorCode_BadRequest);
-      }
-
-      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
-      {
-        LOG(ERROR) << "When modifying a patient, the SopInstanceUID cannot be manually modified";
-        throw OrthancException(ErrorCode_BadRequest);
-      }
-    }
-
-
-    // Sanity checks at the study level
-    if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID))
-    {
-      LOG(ERROR) << "When modifying a study, the parent PatientID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-
-    if (!allowManualIdentifiers_)
-    {
-      if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
-      {
-        LOG(ERROR) << "When modifying a study, the SeriesInstanceUID cannot be manually modified";
-        throw OrthancException(ErrorCode_BadRequest);
-      }
-
-      if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
-      {
-        LOG(ERROR) << "When modifying a study, the SopInstanceUID cannot be manually modified";
-        throw OrthancException(ErrorCode_BadRequest);
-      }
-    }
-
-
-    // Sanity checks at the series level
-    if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID))
-    {
-      LOG(ERROR) << "When modifying a series, the parent PatientID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-
-    if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
-    {
-      LOG(ERROR) << "When modifying a series, the parent StudyInstanceUID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-
-    if (!allowManualIdentifiers_)
-    {
-      if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
-      {
-        LOG(ERROR) << "When modifying a series, the SopInstanceUID cannot be manually modified";
-        throw OrthancException(ErrorCode_BadRequest);
-      }
-    }
-
-
-    // Sanity checks at the instance level
-    if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID))
-    {
-      LOG(ERROR) << "When modifying an instance, the parent PatientID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-
-    if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
-    {
-      LOG(ERROR) << "When modifying an instance, the parent StudyInstanceUID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-
-    if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
-    {
-      LOG(ERROR) << "When modifying an instance, the parent SeriesInstanceUID cannot be manually modified";
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-
-
-    // (1) Remove the private tags, if need be
-    if (removePrivateTags_)
-    {
-      toModify.RemovePrivateTags(privateTagsToKeep_);
-    }
-
-    // (2) Clear the tags specified by the user
-    for (SetOfTags::const_iterator it = clearings_.begin(); 
-         it != clearings_.end(); ++it)
-    {
-      toModify.Clear(*it, true /* only clear if the tag exists in the original file */);
-    }
-
-    // (3) Remove the tags specified by the user
-    for (SetOfTags::const_iterator it = removals_.begin(); 
-         it != removals_.end(); ++it)
-    {
-      toModify.Remove(*it);
-    }
-
-    // (4) Replace the tags
-    for (Replacements::const_iterator it = replacements_.begin(); 
-         it != replacements_.end(); ++it)
-    {
-      toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent);
-    }
-
-    // (5) Update the DICOM identifiers
-    if (level_ <= ResourceType_Study &&
-        !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
-    {
-      if (keepStudyInstanceUid_)
-      {
-        LOG(WARNING) << "Modifying a study while keeping its original StudyInstanceUID: This should be avoided!";
-      }
-      else
-      {
-        MapDicomIdentifier(toModify, ResourceType_Study);
-      }
-    }
-
-    if (level_ <= ResourceType_Series &&
-        !IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
-    {
-      if (keepSeriesInstanceUid_)
-      {
-        LOG(WARNING) << "Modifying a series while keeping its original SeriesInstanceUID: This should be avoided!";
-      }
-      else
-      {
-        MapDicomIdentifier(toModify, ResourceType_Series);
-      }
-    }
-
-    if (level_ <= ResourceType_Instance &&  // Always true
-        !IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
-    {
-      MapDicomIdentifier(toModify, ResourceType_Instance);
-    }
-  }
-}
--- a/OrthancServer/DicomModification.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,137 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ParsedDicomFile.h"
-
-namespace Orthanc
-{
-  class DicomModification : public boost::noncopyable
-  {
-    /**
-     * Process:
-     * (1) Remove private tags
-     * (2) Remove tags specified by the user
-     * (3) Replace tags
-     **/
-
-  private:
-    typedef std::set<DicomTag> SetOfTags;
-    typedef std::map<DicomTag, Json::Value*> Replacements;
-    typedef std::map< std::pair<ResourceType, std::string>, std::string>  UidMap;
-
-    SetOfTags removals_;
-    SetOfTags clearings_;
-    Replacements replacements_;
-    bool removePrivateTags_;
-    ResourceType level_;
-    UidMap uidMap_;
-    SetOfTags privateTagsToKeep_;
-    bool allowManualIdentifiers_;
-    bool keepStudyInstanceUid_;
-    bool keepSeriesInstanceUid_;
-
-    void MapDicomIdentifier(ParsedDicomFile& dicom,
-                            ResourceType level);
-
-    void MarkNotOrthancAnonymization();
-
-    void ClearReplacements();
-
-    bool CancelReplacement(const DicomTag& tag);
-
-    void ReplaceInternal(const DicomTag& tag,
-                         const Json::Value& value);
-
-    void SetupAnonymization2008();
-
-    void SetupAnonymization2017c();
-
-  public:
-    DicomModification();
-
-    ~DicomModification();
-
-    void Keep(const DicomTag& tag);
-
-    void Remove(const DicomTag& tag);
-
-    // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization)
-    void Clear(const DicomTag& tag);
-
-    bool IsRemoved(const DicomTag& tag) const;
-
-    bool IsCleared(const DicomTag& tag) const;
-
-    // "safeForAnonymization" tells Orthanc that this replacement does
-    // not break the anonymization process it implements (for internal use only)
-    void Replace(const DicomTag& tag,
-                 const Json::Value& value,   // Encoded using UTF-8
-                 bool safeForAnonymization);
-
-    bool IsReplaced(const DicomTag& tag) const;
-
-    const Json::Value& GetReplacement(const DicomTag& tag) const;
-
-    std::string GetReplacementAsString(const DicomTag& tag) const;
-
-    void SetRemovePrivateTags(bool removed);
-
-    bool ArePrivateTagsRemoved() const
-    {
-      return removePrivateTags_;
-    }
-
-    void SetLevel(ResourceType level);
-
-    ResourceType GetLevel() const
-    {
-      return level_;
-    }
-
-    void SetupAnonymization(DicomVersion version);
-
-    void Apply(ParsedDicomFile& toModify);
-
-    void SetAllowManualIdentifiers(bool check)
-    {
-      allowManualIdentifiers_ = check;
-    }
-
-    bool AreAllowManualIdentifiers() const
-    {
-      return allowManualIdentifiers_;
-    }
-  };
-}
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,179 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "DicomFindAnswers.h"
-
-#include "../OrthancInitialization.h"
-#include "../FromDcmtkBridge.h"
-#include "../../Core/OrthancException.h"
-
-#include <memory>
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <boost/noncopyable.hpp>
-
-
-namespace Orthanc
-{
-  void DicomFindAnswers::AddAnswerInternal(ParsedDicomFile* answer)
-  {
-    std::auto_ptr<ParsedDicomFile> protection(answer);
-
-    if (isWorklist_)
-    {
-      // These lines are necessary when serving worklists, otherwise
-      // Orthanc does not behave as "wlmscpfs"
-      protection->Remove(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID);
-      protection->Remove(DICOM_TAG_SOP_INSTANCE_UID);
-    }
-
-    protection->ChangeEncoding(encoding_);
-
-    answers_.push_back(protection.release());
-  }
-
-
-  DicomFindAnswers::DicomFindAnswers(bool isWorklist) : 
-    encoding_(GetDefaultDicomEncoding()),
-    isWorklist_(isWorklist),
-    complete_(true)
-  {
-  }
-
-
-  void DicomFindAnswers::SetEncoding(Encoding encoding)
-  {
-    for (size_t i = 0; i < answers_.size(); i++)
-    {
-      assert(answers_[i] != NULL);
-      answers_[i]->ChangeEncoding(encoding);
-    }
-
-    encoding_ = encoding;
-  }
-
-
-  void DicomFindAnswers::SetWorklist(bool isWorklist)
-  {
-    if (answers_.empty())
-    {
-      isWorklist_ = isWorklist;
-    }
-    else
-    {
-      // This set of answers is not empty anymore, cannot change its type
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-  }
-
-
-  void DicomFindAnswers::Clear()
-  {
-    for (size_t i = 0; i < answers_.size(); i++)
-    {
-      assert(answers_[i] != NULL);
-      delete answers_[i];
-    }
-
-    answers_.clear();
-  }
-
-
-  void DicomFindAnswers::Reserve(size_t size)
-  {
-    if (size > answers_.size())
-    {
-      answers_.reserve(size);
-    }
-  }
-
-
-  void DicomFindAnswers::Add(const DicomMap& map)
-  {
-    AddAnswerInternal(new ParsedDicomFile(map, encoding_));
-  }
-
-
-  void DicomFindAnswers::Add(ParsedDicomFile& dicom)
-  {
-    AddAnswerInternal(dicom.Clone());
-  }
-
-  void DicomFindAnswers::Add(const void* dicom,
-                             size_t size)
-  {
-    AddAnswerInternal(new ParsedDicomFile(dicom, size));
-  }
-
-
-  ParsedDicomFile& DicomFindAnswers::GetAnswer(size_t index) const
-  {
-    if (index < answers_.size())
-    {
-      return *answers_[index];
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  DcmDataset* DicomFindAnswers::ExtractDcmDataset(size_t index) const
-  {
-    return new DcmDataset(*GetAnswer(index).GetDcmtkObject().getDataset());
-  }
-
-
-  void DicomFindAnswers::ToJson(Json::Value& target,
-                                size_t index,
-                                bool simplify) const
-  {
-    DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Human : DicomToJsonFormat_Full);
-    GetAnswer(index).DatasetToJson(target, format, DicomToJsonFlags_None, 0);
-  }
-
-
-  void DicomFindAnswers::ToJson(Json::Value& target,
-                                bool simplify) const
-  {
-    target = Json::arrayValue;
-
-    for (size_t i = 0; i < GetSize(); i++)
-    {
-      Json::Value answer;
-      ToJson(answer, i, simplify);
-      target.append(answer);
-    }
-  }
-}
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * 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 DicomFindAnswers : public boost::noncopyable
-  {
-  private:
-    Encoding                      encoding_;
-    bool                          isWorklist_;
-    std::vector<ParsedDicomFile*> answers_;
-    bool                          complete_;
-
-    void AddAnswerInternal(ParsedDicomFile* answer);
-
-  public:
-    DicomFindAnswers(bool isWorklist);
-
-    ~DicomFindAnswers()
-    {
-      Clear();
-    }
-
-    Encoding GetEncoding() const
-    {
-      return encoding_;
-    }
-
-    void SetEncoding(Encoding encoding);
-
-    void SetWorklist(bool isWorklist);
-
-    bool IsWorklist() const
-    {
-      return isWorklist_;
-    }
-
-    void Clear();
-
-    void Reserve(size_t index);
-
-    void Add(const DicomMap& map);
-
-    void Add(ParsedDicomFile& dicom);
-
-    void Add(const void* dicom,
-             size_t size);
-
-    size_t GetSize() const
-    {
-      return answers_.size();
-    }
-
-    ParsedDicomFile& GetAnswer(size_t index) const;
-
-    DcmDataset* ExtractDcmDataset(size_t index) const;
-
-    void ToJson(Json::Value& target,
-                bool simplify) const;
-
-    void ToJson(Json::Value& target,
-                size_t index,
-                bool simplify) const;
-
-    bool IsComplete() const
-    {
-      return complete_;
-    }
-
-    void SetComplete(bool isComplete)
-    {
-      complete_ = isComplete;
-    }
-  };
-}
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,382 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "DicomServer.h"
-
-#include "../../Core/Logging.h"
-#include "../../Core/MultiThreading/RunnableWorkersPool.h"
-#include "../../Core/OrthancException.h"
-#include "../../Core/Toolbox.h"
-#include "../Internals/CommandDispatcher.h"
-
-#include <boost/thread.hpp>
-
-#if defined(__linux__)
-#include <cstdlib>
-#endif
-
-
-namespace Orthanc
-{
-  struct DicomServer::PImpl
-  {
-    boost::thread  thread_;
-    T_ASC_Network *network_;
-    std::auto_ptr<RunnableWorkersPool>  workers_;
-  };
-
-
-  void DicomServer::ServerThread(DicomServer* server)
-  {
-    LOG(INFO) << "DICOM server started";
-
-    while (server->continue_)
-    {
-      /* receive an association and acknowledge or reject it. If the association was */
-      /* acknowledged, offer corresponding services and invoke one or more if required. */
-      std::auto_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_));
-
-      try
-      {
-        if (dispatcher.get() != NULL)
-        {
-          server->pimpl_->workers_->Add(dispatcher.release());
-        }
-      }
-      catch (OrthancException& e)
-      {
-        LOG(ERROR) << "Exception in the DICOM server thread: " << e.What();
-      }
-    }
-
-    LOG(INFO) << "DICOM server stopping";
-  }
-
-
-  DicomServer::DicomServer() : 
-    pimpl_(new PImpl),
-    aet_("ANY-SCP")
-  {
-    port_ = 104;
-    modalities_ = NULL;
-    findRequestHandlerFactory_ = NULL;
-    moveRequestHandlerFactory_ = NULL;
-    storeRequestHandlerFactory_ = NULL;
-    worklistRequestHandlerFactory_ = NULL;
-    applicationEntityFilter_ = NULL;
-    checkCalledAet_ = true;
-    associationTimeout_ = 30;
-    continue_ = false;
-  }
-
-  DicomServer::~DicomServer()
-  {
-    if (continue_)
-    {
-      LOG(ERROR) << "INTERNAL ERROR: DicomServer::Stop() should be invoked manually to avoid mess in the destruction order!";
-      Stop();
-    }
-  }
-
-  void DicomServer::SetPortNumber(uint16_t port)
-  {
-    Stop();
-    port_ = port;
-  }
-
-  uint16_t DicomServer::GetPortNumber() const
-  {
-    return port_;
-  }
-
-  void DicomServer::SetAssociationTimeout(uint32_t seconds)
-  {
-    LOG(INFO) << "Setting timeout for DICOM connections if Orthanc acts as SCP (server): " 
-              << seconds << " seconds (0 = no timeout)";
-
-    Stop();
-    associationTimeout_ = seconds;
-  }
-
-  uint32_t DicomServer::GetAssociationTimeout() const
-  {
-    return associationTimeout_;
-  }
-
-
-  void DicomServer::SetCalledApplicationEntityTitleCheck(bool check)
-  {
-    Stop();
-    checkCalledAet_ = check;
-  }
-
-  bool DicomServer::HasCalledApplicationEntityTitleCheck() const
-  {
-    return checkCalledAet_;
-  }
-
-  void DicomServer::SetApplicationEntityTitle(const std::string& aet)
-  {
-    if (aet.size() == 0)
-    {
-      throw OrthancException(ErrorCode_BadApplicationEntityTitle);
-    }
-
-    if (aet.size() > 16)
-    {
-      throw OrthancException(ErrorCode_BadApplicationEntityTitle);
-    }
-
-    for (size_t i = 0; i < aet.size(); i++)
-    {
-      if (!(aet[i] == '-' ||
-            aet[i] == '_' ||
-            isdigit(aet[i]) ||
-            (aet[i] >= 'A' && aet[i] <= 'Z')))
-      {
-        LOG(WARNING) << "For best interoperability, only upper case, alphanumeric characters should be present in AET: \"" << aet << "\"";
-        break;
-      }
-    }
-
-    Stop();
-    aet_ = aet;
-  }
-
-  const std::string& DicomServer::GetApplicationEntityTitle() const
-  {
-    return aet_;
-  }
-
-  void DicomServer::SetRemoteModalities(IRemoteModalities& modalities)
-  {
-    Stop();
-    modalities_ = &modalities;
-  }
-  
-  DicomServer::IRemoteModalities& DicomServer::GetRemoteModalities() const
-  {
-    if (modalities_ == NULL)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      return *modalities_;
-    }
-  }
-    
-  void DicomServer::SetFindRequestHandlerFactory(IFindRequestHandlerFactory& factory)
-  {
-    Stop();
-    findRequestHandlerFactory_ = &factory;
-  }
-
-  bool DicomServer::HasFindRequestHandlerFactory() const
-  {
-    return (findRequestHandlerFactory_ != NULL);
-  }
-
-  IFindRequestHandlerFactory& DicomServer::GetFindRequestHandlerFactory() const
-  {
-    if (HasFindRequestHandlerFactory())
-    {
-      return *findRequestHandlerFactory_;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_NoCFindHandler);
-    }
-  }
-
-  void DicomServer::SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& factory)
-  {
-    Stop();
-    moveRequestHandlerFactory_ = &factory;
-  }
-
-  bool DicomServer::HasMoveRequestHandlerFactory() const
-  {
-    return (moveRequestHandlerFactory_ != NULL);
-  }
-
-  IMoveRequestHandlerFactory& DicomServer::GetMoveRequestHandlerFactory() const
-  {
-    if (HasMoveRequestHandlerFactory())
-    {
-      return *moveRequestHandlerFactory_;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_NoCMoveHandler);
-    }
-  }
-
-  void DicomServer::SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& factory)
-  {
-    Stop();
-    storeRequestHandlerFactory_ = &factory;
-  }
-
-  bool DicomServer::HasStoreRequestHandlerFactory() const
-  {
-    return (storeRequestHandlerFactory_ != NULL);
-  }
-
-  IStoreRequestHandlerFactory& DicomServer::GetStoreRequestHandlerFactory() const
-  {
-    if (HasStoreRequestHandlerFactory())
-    {
-      return *storeRequestHandlerFactory_;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_NoCStoreHandler);
-    }
-  }
-
-  void DicomServer::SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& factory)
-  {
-    Stop();
-    worklistRequestHandlerFactory_ = &factory;
-  }
-
-  bool DicomServer::HasWorklistRequestHandlerFactory() const
-  {
-    return (worklistRequestHandlerFactory_ != NULL);
-  }
-
-  IWorklistRequestHandlerFactory& DicomServer::GetWorklistRequestHandlerFactory() const
-  {
-    if (HasWorklistRequestHandlerFactory())
-    {
-      return *worklistRequestHandlerFactory_;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_NoWorklistHandler);
-    }
-  }
-
-  void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory)
-  {
-    Stop();
-    applicationEntityFilter_ = &factory;
-  }
-
-  bool DicomServer::HasApplicationEntityFilter() const
-  {
-    return (applicationEntityFilter_ != NULL);
-  }
-
-  IApplicationEntityFilter& DicomServer::GetApplicationEntityFilter() const
-  {
-    if (HasApplicationEntityFilter())
-    {
-      return *applicationEntityFilter_;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_NoApplicationEntityFilter);
-    }
-  }
-
-  void DicomServer::Start()
-  {
-    if (modalities_ == NULL)
-    {
-      LOG(ERROR) << "No list of modalities was provided to the DICOM server";
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    
-    Stop();
-
-    /* initialize network, i.e. create an instance of T_ASC_Network*. */
-    OFCondition cond = ASC_initializeNetwork
-      (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_);
-    if (cond.bad())
-    {
-      LOG(ERROR) << "cannot create network: " << cond.text();
-      throw OrthancException(ErrorCode_DicomPortInUse);
-    }
-
-    continue_ = true;
-    pimpl_->workers_.reset(new RunnableWorkersPool(4));   // Use 4 workers - TODO as a parameter?
-    pimpl_->thread_ = boost::thread(ServerThread, this);
-  }
-
-
-  void DicomServer::Stop()
-  {
-    if (continue_)
-    {
-      continue_ = false;
-
-      if (pimpl_->thread_.joinable())
-      {
-        pimpl_->thread_.join();
-      }
-
-      pimpl_->workers_.reset(NULL);
-
-      /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
-      /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
-      OFCondition cond = ASC_dropNetwork(&pimpl_->network_);
-      if (cond.bad())
-      {
-        LOG(ERROR) << "Error while dropping the network: " << cond.text();
-      }
-    }
-  }
-
-
-  bool DicomServer::IsMyAETitle(const std::string& aet) const
-  {
-    if (modalities_ == NULL)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    
-    if (!HasCalledApplicationEntityTitleCheck())
-    {
-      // OK, no check on the AET.
-      return true;
-    }
-    else
-    {
-      return modalities_->IsSameAETitle(aet, GetApplicationEntityTitle());
-    }
-  }
-
-}
--- a/OrthancServer/DicomProtocol/DicomServer.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,136 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1
-#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1
-#endif
-
-#include "IFindRequestHandlerFactory.h"
-#include "IMoveRequestHandlerFactory.h"
-#include "IStoreRequestHandlerFactory.h"
-#include "IWorklistRequestHandlerFactory.h"
-#include "IApplicationEntityFilter.h"
-#include "RemoteModalityParameters.h"
-
-#include <boost/shared_ptr.hpp>
-#include <boost/noncopyable.hpp>
-
-
-namespace Orthanc
-{
-  class DicomServer : public boost::noncopyable
-  {
-  public:
-    // WARNING: The methods of this class must be thread-safe
-    class IRemoteModalities : public boost::noncopyable
-    {
-    public:
-      virtual ~IRemoteModalities()
-      {
-      }
-      
-      virtual bool IsSameAETitle(const std::string& aet1,
-                                 const std::string& aet2) = 0;
-
-      virtual bool LookupAETitle(RemoteModalityParameters& modality,
-                                 const std::string& aet) = 0;
-    };
-    
-  private:
-    struct PImpl;
-    boost::shared_ptr<PImpl> pimpl_;
-
-    bool checkCalledAet_;
-    std::string aet_;
-    uint16_t port_;
-    bool continue_;
-    uint32_t associationTimeout_;
-    IRemoteModalities* modalities_;
-    IFindRequestHandlerFactory* findRequestHandlerFactory_;
-    IMoveRequestHandlerFactory* moveRequestHandlerFactory_;
-    IStoreRequestHandlerFactory* storeRequestHandlerFactory_;
-    IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_;
-    IApplicationEntityFilter* applicationEntityFilter_;
-
-    static void ServerThread(DicomServer* server);
-
-  public:
-    DicomServer();
-
-    ~DicomServer();
-
-    void SetPortNumber(uint16_t port);
-    uint16_t GetPortNumber() const;
-
-    void SetAssociationTimeout(uint32_t seconds);
-    uint32_t GetAssociationTimeout() const;
-
-    void SetCalledApplicationEntityTitleCheck(bool check);
-    bool HasCalledApplicationEntityTitleCheck() const;
-
-    void SetApplicationEntityTitle(const std::string& aet);
-    const std::string& GetApplicationEntityTitle() const;
-
-    void SetRemoteModalities(IRemoteModalities& modalities);
-    IRemoteModalities& GetRemoteModalities() const;
-    
-    void SetFindRequestHandlerFactory(IFindRequestHandlerFactory& handler);
-    bool HasFindRequestHandlerFactory() const;
-    IFindRequestHandlerFactory& GetFindRequestHandlerFactory() const;
-
-    void SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& handler);
-    bool HasMoveRequestHandlerFactory() const;
-    IMoveRequestHandlerFactory& GetMoveRequestHandlerFactory() const;
-
-    void SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& handler);
-    bool HasStoreRequestHandlerFactory() const;
-    IStoreRequestHandlerFactory& GetStoreRequestHandlerFactory() const;
-
-    void SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& handler);
-    bool HasWorklistRequestHandlerFactory() const;
-    IWorklistRequestHandlerFactory& GetWorklistRequestHandlerFactory() const;
-
-    void SetApplicationEntityFilter(IApplicationEntityFilter& handler);
-    bool HasApplicationEntityFilter() const;
-    IApplicationEntityFilter& GetApplicationEntityFilter() const;
-
-    void Start();
-  
-    void Stop();
-
-    bool IsMyAETitle(const std::string& aet) const;
-  };
-
-}
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1220 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-
-/*=========================================================================
-
-  This file is based on portions of the following project:
-
-  Program: DCMTK 3.6.0
-  Module:  http://dicom.offis.de/dcmtk.php.en
-
-Copyright (C) 1994-2011, OFFIS e.V.
-All rights reserved.
-
-This software and supporting documentation were developed by
-
-  OFFIS e.V.
-  R&D Division Health
-  Escherweg 2
-  26121 Oldenburg, Germany
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-- Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-- Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-- Neither the name of OFFIS nor the names of its contributors may be
-  used to endorse or promote products derived from this software
-  without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-=========================================================================*/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "DicomUserConnection.h"
-
-#include "../../Core/DicomFormat/DicomArray.h"
-#include "../../Core/Logging.h"
-#include "../../Core/OrthancException.h"
-#include "../FromDcmtkBridge.h"
-#include "../ToDcmtkBridge.h"
-
-#include <dcmtk/dcmdata/dcistrmb.h>
-#include <dcmtk/dcmdata/dcistrmf.h>
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <dcmtk/dcmdata/dcmetinf.h>
-#include <dcmtk/dcmnet/diutil.h>
-
-#include <set>
-
-
-#ifdef _WIN32
-/**
- * "The maximum length, in bytes, of the string returned in the buffer 
- * pointed to by the name parameter is dependent on the namespace provider,
- * but this string must be 256 bytes or less.
- * http://msdn.microsoft.com/en-us/library/windows/desktop/ms738527(v=vs.85).aspx
- **/
-#  define HOST_NAME_MAX 256
-#  include <winsock.h>
-#endif 
-
-
-#if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX)
-/**
- * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that
- * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an
- * array of size _POSIX_HOST_NAME_MAX, invoke gethostname() and expect
- * that the result will fit."
- * http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00128.html
- **/
-#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
-#endif
-
-
-static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax;
-
-/**
- * "If we have more than 64 storage SOP classes, tools such as
- * storescu will fail because they attempt to negotiate two
- * presentation contexts for each SOP class, and there is a total
- * limit of 128 contexts for one association."
- **/
-static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64;
-
-
-namespace Orthanc
-{
-  // By default, the timeout for DICOM SCU (client) connections is set to 10 seconds
-  static uint32_t defaultTimeout_ = 10;
-
-  struct DicomUserConnection::PImpl
-  {
-    // Connection state
-    uint32_t dimseTimeout_;
-    uint32_t acseTimeout_;
-    T_ASC_Network* net_;
-    T_ASC_Parameters* params_;
-    T_ASC_Association* assoc_;
-
-    bool IsOpen() const
-    {
-      return assoc_ != NULL;
-    }
-
-    void CheckIsOpen() const;
-
-    void Store(DcmInputStream& is, 
-               DicomUserConnection& connection,
-               const std::string& moveOriginatorAET,
-               uint16_t moveOriginatorID);
-  };
-
-
-  static void Check(const OFCondition& cond)
-  {
-    if (cond.bad())
-    {
-      LOG(ERROR) << "DicomUserConnection: " << std::string(cond.text());
-       throw OrthancException(ErrorCode_NetworkProtocol);
-    }
-  }
-
-  void DicomUserConnection::PImpl::CheckIsOpen() const
-  {
-    if (!IsOpen())
-    {
-      LOG(ERROR) << "DicomUserConnection: First open the connection";
-      throw OrthancException(ErrorCode_NetworkProtocol);
-    }
-  }
-
-
-  void DicomUserConnection::CheckIsOpen() const
-  {
-    pimpl_->CheckIsOpen();
-  }
-
-
-  static void RegisterStorageSOPClass(T_ASC_Parameters* params,
-                                      unsigned int& presentationContextId,
-                                      const std::string& sopClass,
-                                      const char* asPreferred[],
-                                      std::vector<const char*>& asFallback)
-  {
-    Check(ASC_addPresentationContext(params, presentationContextId, 
-                                     sopClass.c_str(), asPreferred, 1));
-    presentationContextId += 2;
-
-    if (asFallback.size() > 0)
-    {
-      Check(ASC_addPresentationContext(params, presentationContextId, 
-                                       sopClass.c_str(), &asFallback[0], asFallback.size()));
-      presentationContextId += 2;
-    }
-  }
-  
-    
-  void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax)
-  {
-    // Flatten an array with the preferred transfer syntax
-    const char* asPreferred[1] = { preferredTransferSyntax.c_str() };
-
-    // Setup the fallback transfer syntaxes
-    std::set<std::string> fallbackSyntaxes;
-    fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax);
-    fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax);
-    fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax);
-    fallbackSyntaxes.erase(preferredTransferSyntax);
-
-    // Flatten an array with the fallback transfer syntaxes
-    std::vector<const char*> asFallback;
-    asFallback.reserve(fallbackSyntaxes.size());
-    for (std::set<std::string>::const_iterator 
-           it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it)
-    {
-      asFallback.push_back(it->c_str());
-    }
-
-    CheckStorageSOPClassesInvariant();
-    unsigned int presentationContextId = 1;
-
-    for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin();
-         it != reservedStorageSOPClasses_.end(); ++it)
-    {
-      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                              *it, asPreferred, asFallback);
-    }
-
-    for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin();
-         it != storageSOPClasses_.end(); ++it)
-    {
-      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                              *it, asPreferred, asFallback);
-    }
-
-    for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin();
-         it != defaultStorageSOPClasses_.end(); ++it)
-    {
-      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                              *it, asPreferred, asFallback);
-    }
-  }
-
-
-  static bool IsGenericTransferSyntax(const std::string& syntax)
-  {
-    return (syntax == UID_LittleEndianExplicitTransferSyntax ||
-            syntax == UID_BigEndianExplicitTransferSyntax ||
-            syntax == UID_LittleEndianImplicitTransferSyntax);
-  }
-
-
-  void DicomUserConnection::PImpl::Store(DcmInputStream& is, 
-                                         DicomUserConnection& connection,
-                                         const std::string& moveOriginatorAET,
-                                         uint16_t moveOriginatorID)
-  {
-    CheckIsOpen();
-
-    DcmFileFormat dcmff;
-    Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength));
-
-    // Determine the storage SOP class UID for this instance
-    static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016);
-    OFString sopClassUid;
-    if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good())
-    {
-      connection.AddStorageSOPClass(sopClassUid.c_str());
-    }
-
-    // Determine whether a new presentation context must be
-    // negotiated, depending on the transfer syntax of this instance
-    DcmXfer xfer(dcmff.getDataset()->getOriginalXfer());
-    const std::string syntax(xfer.getXferID());
-    bool isGeneric = IsGenericTransferSyntax(syntax);
-
-    bool renegotiate;
-    if (isGeneric)
-    {
-      // Are we making a generic-to-specific or specific-to-generic change of
-      // the transfer syntax? If this is the case, renegotiate the connection.
-      renegotiate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax());
-    }
-    else
-    {
-      // We are using a specific transfer syntax. Renegotiate if the
-      // current connection does not match this transfer syntax.
-      renegotiate = (syntax != connection.GetPreferredTransferSyntax());
-    }
-
-    if (renegotiate)
-    {
-      LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated";
-
-      if (isGeneric)
-      {
-        connection.ResetPreferredTransferSyntax();
-      }
-      else
-      {
-        connection.SetPreferredTransferSyntax(syntax);
-      }
-    }
-
-    if (!connection.IsOpen())
-    {
-      LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters";
-      connection.Open();
-    }
-
-    // Figure out which SOP class and SOP instance is encapsulated in the file
-    DIC_UI sopClass;
-    DIC_UI sopInstance;
-    if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance))
-    {
-      throw OrthancException(ErrorCode_NoSopClassOrInstance);
-    }
-
-    // Figure out which of the accepted presentation contexts should be used
-    int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass);
-    if (presID == 0)
-    {
-      const char *modalityName = dcmSOPClassUIDToModality(sopClass);
-      if (!modalityName) modalityName = dcmFindNameOfUID(sopClass);
-      if (!modalityName) modalityName = "unknown SOP class";
-      throw OrthancException(ErrorCode_NoPresentationContext);
-    }
-
-    // Prepare the transmission of data
-    T_DIMSE_C_StoreRQ request;
-    memset(&request, 0, sizeof(request));
-    request.MessageID = assoc_->nextMsgID++;
-    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
-    request.Priority = DIMSE_PRIORITY_MEDIUM;
-    request.DataSetType = DIMSE_DATASET_PRESENT;
-    strncpy(request.AffectedSOPInstanceUID, sopInstance, DIC_UI_LEN);
-
-    if (!moveOriginatorAET.empty())
-    {
-      strncpy(request.MoveOriginatorApplicationEntityTitle, 
-              moveOriginatorAET.c_str(), DIC_AE_LEN);
-      request.opts = O_STORE_MOVEORIGINATORAETITLE;
-
-      request.MoveOriginatorID = moveOriginatorID;  // The type DIC_US is an alias for uint16_t
-      request.opts |= O_STORE_MOVEORIGINATORID;
-    }
-
-    // Finally conduct transmission of data
-    T_DIMSE_C_StoreRSP rsp;
-    DcmDataset* statusDetail = NULL;
-    Check(DIMSE_storeUser(assoc_, presID, &request,
-                          NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL,
-                          /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ dimseTimeout_,
-                          &rsp, &statusDetail, NULL));
-
-    if (statusDetail != NULL) 
-    {
-      delete statusDetail;
-    }
-  }
-
-
-  namespace
-  {
-    struct FindPayload
-    {
-      DicomFindAnswers* answers;
-      const char*       level;
-      bool              isWorklist;
-    };
-  }
-
-
-  static void FindCallback(
-    /* in */
-    void *callbackData,
-    T_DIMSE_C_FindRQ *request,      /* original find request */
-    int responseCount,
-    T_DIMSE_C_FindRSP *response,    /* pending response received */
-    DcmDataset *responseIdentifiers /* pending response identifiers */
-    )
-  {
-    FindPayload& payload = *reinterpret_cast<FindPayload*>(callbackData);
-
-    if (responseIdentifiers != NULL)
-    {
-      if (payload.isWorklist)
-      {
-        ParsedDicomFile answer(*responseIdentifiers);
-        payload.answers->Add(answer);
-      }
-      else
-      {
-        DicomMap m;
-        FromDcmtkBridge::ExtractDicomSummary(m, *responseIdentifiers);
-        
-        if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL))
-        {
-          m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level, false);
-        }
-
-        payload.answers->Add(m);
-      }
-    }
-  }
-
-
-  static void FixFindQuery(DicomMap& fixedQuery,
-                           ResourceType level,
-                           const DicomMap& fields)
-  {
-    std::set<DicomTag> allowedTags;
-
-    // WARNING: Do not add "break" or reorder items in this switch-case!
-    switch (level)
-    {
-      case ResourceType_Instance:
-        DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance);
-
-      case ResourceType_Series:
-        DicomTag::AddTagsForModule(allowedTags, DicomModule_Series);
-
-      case ResourceType_Study:
-        DicomTag::AddTagsForModule(allowedTags, DicomModule_Study);
-
-      case ResourceType_Patient:
-        DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    switch (level)
-    {
-      case ResourceType_Patient:
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES);
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES);
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES);
-        break;
-
-      case ResourceType_Study:
-        allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY);
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES);
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES);
-        allowedTags.insert(DICOM_TAG_SOP_CLASSES_IN_STUDY);
-        break;
-
-      case ResourceType_Series:
-        allowedTags.insert(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES);
-        break;
-
-      default:
-        break;
-    }
-
-    allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET);
-
-    DicomArray query(fields);
-    for (size_t i = 0; i < query.GetSize(); i++)
-    {
-      const DicomTag& tag = query.GetElement(i).GetTag();
-      if (allowedTags.find(tag) == allowedTags.end())
-      {
-        LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag;
-      }
-      else
-      {
-        fixedQuery.SetValue(tag, query.GetElement(i).GetValue());
-      }
-    }
-  }
-
-
-  static ParsedDicomFile* ConvertQueryFields(const DicomMap& fields,
-                                             ModalityManufacturer manufacturer)
-  {
-    // Fix outgoing C-Find requests issue for Syngo.Via and its
-    // solution was reported by Emsy Chan by private mail on
-    // 2015-06-17. According to Robert van Ommen (2015-11-30), the
-    // same fix is required for Agfa Impax. This was generalized for
-    // generic manufacturer since it seems to affect PhilipsADW,
-    // GEWAServer as well:
-    // https://bitbucket.org/sjodogne/orthanc/issues/31/
-
-    switch (manufacturer)
-    {
-      case ModalityManufacturer_GenericNoWildcardInDates:
-      case ModalityManufacturer_GenericNoUniversalWildcard:
-      {
-        std::auto_ptr<DicomMap> fix(fields.Clone());
-
-        std::set<DicomTag> tags;
-        fix->GetTags(tags);
-
-        for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it)
-        {
-          // Replace a "*" wildcard query by an empty query ("") for
-          // "date" or "all" value representations depending on the
-          // type of manufacturer.
-          if (manufacturer == ModalityManufacturer_GenericNoUniversalWildcard ||
-              (manufacturer == ModalityManufacturer_GenericNoWildcardInDates &&
-               FromDcmtkBridge::LookupValueRepresentation(*it) == ValueRepresentation_Date))
-          {
-            const DicomValue* value = fix->TestAndGetValue(*it);
-
-            if (value != NULL && 
-                !value->IsNull() &&
-                value->GetContent() == "*")
-            {
-              fix->SetValue(*it, "", false);
-            }
-          }
-        }
-
-        return new ParsedDicomFile(*fix);
-      }
-
-      default:
-        return new ParsedDicomFile(fields);
-    }
-  }
-
-
-  static void ExecuteFind(DicomFindAnswers& answers,
-                          T_ASC_Association* association,
-                          DcmDataset* dataset,
-                          const char* sopClass,
-                          bool isWorklist,
-                          const char* level,
-                          uint32_t dimseTimeout)
-  {
-    assert(isWorklist ^ (level != NULL));
-
-    FindPayload payload;
-    payload.answers = &answers;
-    payload.level = level;
-    payload.isWorklist = isWorklist;
-
-    // Figure out which of the accepted presentation contexts should be used
-    int presID = ASC_findAcceptedPresentationContextID(association, sopClass);
-    if (presID == 0)
-    {
-      throw OrthancException(ErrorCode_DicomFindUnavailable);
-    }
-
-    T_DIMSE_C_FindRQ request;
-    memset(&request, 0, sizeof(request));
-    request.MessageID = association->nextMsgID++;
-    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
-    request.Priority = DIMSE_PRIORITY_MEDIUM;
-    request.DataSetType = DIMSE_DATASET_PRESENT;
-
-    T_DIMSE_C_FindRSP response;
-    DcmDataset* statusDetail = NULL;
-    OFCondition cond = DIMSE_findUser(association, presID, &request, dataset,
-                                      FindCallback, &payload,
-                                      /*opt_blockMode*/ DIMSE_BLOCKING, 
-                                      /*opt_dimse_timeout*/ dimseTimeout,
-                                      &response, &statusDetail);
-
-    if (statusDetail)
-    {
-      delete statusDetail;
-    }
-
-    Check(cond);
-  }
-
-
-  void DicomUserConnection::Find(DicomFindAnswers& result,
-                                 ResourceType level,
-                                 const DicomMap& originalFields)
-  {
-    DicomMap fields;
-    FixFindQuery(fields, level, originalFields);
-
-    CheckIsOpen();
-
-    std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_));
-    DcmDataset* dataset = query->GetDcmtkObject().getDataset();
-
-    const char* clevel = NULL;
-    const char* sopClass = NULL;
-
-    switch (level)
-    {
-      case ResourceType_Patient:
-        clevel = "PATIENT";
-        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT");
-        sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
-        break;
-
-      case ResourceType_Study:
-        clevel = "STUDY";
-        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY");
-        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
-        break;
-
-      case ResourceType_Series:
-        clevel = "SERIES";
-        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES");
-        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
-        break;
-
-      case ResourceType_Instance:
-        clevel = "INSTANCE";
-        if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
-            manufacturer_ == ModalityManufacturer_Dcm4Chee)
-        {
-          // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
-          // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
-          // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
-          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE");
-        }
-        else
-        {
-          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE");
-        }
-
-        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    // Add the expected tags for this query level.
-    // WARNING: Do not reorder or add "break" in this switch-case!
-    switch (level)
-    {
-      case ResourceType_Instance:
-        // SOP Instance UID
-        if (!fields.HasTag(0x0008, 0x0018))
-          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0018), "");
-
-      case ResourceType_Series:
-        // Series instance UID
-        if (!fields.HasTag(0x0020, 0x000e))
-          DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000e), "");
-
-      case ResourceType_Study:
-        // Accession number
-        if (!fields.HasTag(0x0008, 0x0050))
-          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0050), "");
-
-        // Study instance UID
-        if (!fields.HasTag(0x0020, 0x000d))
-          DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000d), "");
-
-      case ResourceType_Patient:
-        // Patient ID
-        if (!fields.HasTag(0x0010, 0x0020))
-          DU_putStringDOElement(dataset, DcmTagKey(0x0010, 0x0020), "");
-
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    assert(clevel != NULL && sopClass != NULL);
-    ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, false, clevel, pimpl_->dimseTimeout_);
-  }
-
-
-  void DicomUserConnection::MoveInternal(const std::string& targetAet,
-                                         ResourceType level,
-                                         const DicomMap& fields)
-  {
-    CheckIsOpen();
-
-    std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_));
-    DcmDataset* dataset = query->GetDcmtkObject().getDataset();
-
-    const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel;
-    switch (level)
-    {
-      case ResourceType_Patient:
-        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT");
-        break;
-
-      case ResourceType_Study:
-        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY");
-        break;
-
-      case ResourceType_Series:
-        DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES");
-        break;
-
-      case ResourceType_Instance:
-        if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
-            manufacturer_ == ModalityManufacturer_Dcm4Chee)
-        {
-          // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
-          // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
-          // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
-          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE");
-        }
-        else
-        {
-          DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE");
-        }
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    // Figure out which of the accepted presentation contexts should be used
-    int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass);
-    if (presID == 0)
-    {
-      throw OrthancException(ErrorCode_DicomMoveUnavailable);
-    }
-
-    T_DIMSE_C_MoveRQ request;
-    memset(&request, 0, sizeof(request));
-    request.MessageID = pimpl_->assoc_->nextMsgID++;
-    strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN);
-    request.Priority = DIMSE_PRIORITY_MEDIUM;
-    request.DataSetType = DIMSE_DATASET_PRESENT;
-    strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN);
-
-    T_DIMSE_C_MoveRSP response;
-    DcmDataset* statusDetail = NULL;
-    DcmDataset* responseIdentifiers = NULL;
-    OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset,
-                                      NULL, NULL,
-                                      /*opt_blockMode*/ DIMSE_BLOCKING, 
-                                      /*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
-                                      pimpl_->net_, NULL, NULL,
-                                      &response, &statusDetail, &responseIdentifiers);
-
-    if (statusDetail)
-    {
-      delete statusDetail;
-    }
-
-    if (responseIdentifiers)
-    {
-      delete responseIdentifiers;
-    }
-
-    Check(cond);
-  }
-
-
-  void DicomUserConnection::ResetStorageSOPClasses()
-  {
-    CheckStorageSOPClassesInvariant();
-
-    storageSOPClasses_.clear();
-    defaultStorageSOPClasses_.clear();
-
-    // Copy the short list of storage SOP classes from DCMTK, making
-    // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**).
-
-    std::set<std::string> uncommon;
-    uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage);
-    uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage);
-    uncommon.insert(UID_ColorSoftcopyPresentationStateStorage);
-    uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage);
-    uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage);
-
-    // Add the storage syntaxes for C-STORE
-    for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++)
-    {
-      if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end())
-      {
-        defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]);
-      }
-    }
-
-    CheckStorageSOPClassesInvariant();
-  }
-
-
-  DicomUserConnection::DicomUserConnection() : 
-    pimpl_(new PImpl),
-    preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX),
-    localAet_("STORESCU"),
-    remoteAet_("ANY-SCP"),
-    remoteHost_("127.0.0.1")
-  {
-    remotePort_ = 104;
-    manufacturer_ = ModalityManufacturer_Generic;
-
-    SetTimeout(defaultTimeout_);
-    pimpl_->net_ = NULL;
-    pimpl_->params_ = NULL;
-    pimpl_->assoc_ = NULL;
-
-    // SOP classes for C-ECHO, C-FIND and C-MOVE (**)
-    reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass);
-    reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
-    reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
-    reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
-    reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel);
-
-    ResetStorageSOPClasses();
-  }
-
-  DicomUserConnection::~DicomUserConnection()
-  {
-    Close();
-  }
-
-
-  void DicomUserConnection::SetRemoteModality(const RemoteModalityParameters& parameters)
-  {
-    SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle());
-    SetRemoteHost(parameters.GetHost());
-    SetRemotePort(parameters.GetPort());
-    SetRemoteManufacturer(parameters.GetManufacturer());
-  }
-
-
-  void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet)
-  {
-    if (localAet_ != aet)
-    {
-      Close();
-      localAet_ = aet;
-    }
-  }
-
-  void DicomUserConnection::SetRemoteApplicationEntityTitle(const std::string& aet)
-  {
-    if (remoteAet_ != aet)
-    {
-      Close();
-      remoteAet_ = aet;
-    }
-  }
-
-  void DicomUserConnection::SetRemoteManufacturer(ModalityManufacturer manufacturer)
-  {
-    if (manufacturer_ != manufacturer)
-    {
-      Close();
-      manufacturer_ = manufacturer;
-    }
-  }
-
-  void DicomUserConnection::ResetPreferredTransferSyntax()
-  {
-    SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX);
-  }
-
-  void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax)
-  {
-    if (preferredTransferSyntax_ != preferredTransferSyntax)
-    {
-      Close();
-      preferredTransferSyntax_ = preferredTransferSyntax;
-    }
-  }
-
-
-  void DicomUserConnection::SetRemoteHost(const std::string& host)
-  {
-    if (remoteHost_ != host)
-    {
-      if (host.size() > HOST_NAME_MAX - 10)
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-
-      Close();
-      remoteHost_ = host;
-    }
-  }
-
-  void DicomUserConnection::SetRemotePort(uint16_t port)
-  {
-    if (remotePort_ != port)
-    {
-      Close();
-      remotePort_ = port;
-    }
-  }
-
-  void DicomUserConnection::Open()
-  {
-    if (IsOpen())
-    {
-      // Don't reopen the connection
-      return;
-    }
-
-    LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() 
-              << "\" to AET \"" << GetRemoteApplicationEntityTitle() << "\" on host "
-              << GetRemoteHost() << ":" << GetRemotePort() 
-              << " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")";
-
-    Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_));
-    Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU));
-
-    // Set this application's title and the called application's title in the params
-    Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL));
-
-    // Set the network addresses of the local and remote entities
-    char localHost[HOST_NAME_MAX];
-    gethostname(localHost, HOST_NAME_MAX - 1);
-
-    char remoteHostAndPort[HOST_NAME_MAX];
-
-#ifdef _MSC_VER
-    _snprintf
-#else
-      snprintf
-#endif
-      (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_);
-
-    Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort));
-
-    // Set various options
-    Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false));
-
-    SetupPresentationContexts(preferredTransferSyntax_);
-
-    // Do the association
-    Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_));
-
-    if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0)
-    {
-      throw OrthancException(ErrorCode_NoPresentationContext);
-    }
-  }
-
-  void DicomUserConnection::Close()
-  {
-    if (pimpl_->assoc_ != NULL)
-    {
-      ASC_releaseAssociation(pimpl_->assoc_);
-      ASC_destroyAssociation(&pimpl_->assoc_);
-      pimpl_->assoc_ = NULL;
-      pimpl_->params_ = NULL;
-    }
-    else
-    {
-      if (pimpl_->params_ != NULL)
-      {
-        ASC_destroyAssociationParameters(&pimpl_->params_);
-        pimpl_->params_ = NULL;
-      }
-    }
-
-    if (pimpl_->net_ != NULL)
-    {
-      ASC_dropNetwork(&pimpl_->net_);
-      pimpl_->net_ = NULL;
-    }
-  }
-
-  bool DicomUserConnection::IsOpen() const
-  {
-    return pimpl_->IsOpen();
-  }
-
-  void DicomUserConnection::Store(const char* buffer, 
-                                  size_t size,
-                                  const std::string& moveOriginatorAET,
-                                  uint16_t moveOriginatorID)
-  {
-    // Prepare an input stream for the memory buffer
-    DcmInputBufferStream is;
-    if (size > 0)
-      is.setBuffer(buffer, size);
-    is.setEos();
-      
-    pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID);
-  }
-
-  void DicomUserConnection::Store(const std::string& buffer,
-                                  const std::string& moveOriginatorAET,
-                                  uint16_t moveOriginatorID)
-  {
-    if (buffer.size() > 0)
-      Store(reinterpret_cast<const char*>(&buffer[0]), buffer.size(), moveOriginatorAET, moveOriginatorID);
-    else
-      Store(NULL, 0, moveOriginatorAET, moveOriginatorID);
-  }
-
-  void DicomUserConnection::StoreFile(const std::string& path,
-                                      const std::string& moveOriginatorAET,
-                                      uint16_t moveOriginatorID)
-  {
-    // Prepare an input stream for the file
-    DcmInputFileStream is(path.c_str());
-    pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID);
-  }
-
-  bool DicomUserConnection::Echo()
-  {
-    CheckIsOpen();
-    DIC_US status;
-    Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, 
-                         /*opt_blockMode*/ DIMSE_BLOCKING, 
-                         /*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
-                         &status, NULL));
-    return status == STATUS_Success;
-  }
-
-
-  static void TestAndCopyTag(DicomMap& result,
-                             const DicomMap& source,
-                             const DicomTag& tag)
-  {
-    if (!source.HasTag(tag))
-    {
-      throw OrthancException(ErrorCode_BadRequest);
-    }
-    else
-    {
-      result.SetValue(tag, source.GetValue(tag));
-    }
-  }
-
-
-  void DicomUserConnection::Move(const std::string& targetAet,
-                                 ResourceType level,
-                                 const DicomMap& findResult)
-  {
-    DicomMap move;
-    switch (level)
-    {
-      case ResourceType_Patient:
-        TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID);
-        break;
-
-      case ResourceType_Study:
-        TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID);
-        break;
-
-      case ResourceType_Series:
-        TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID);
-        TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID);
-        break;
-
-      case ResourceType_Instance:
-        TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID);
-        TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID);
-        TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    MoveInternal(targetAet, level, move);
-  }
-
-
-  void DicomUserConnection::Move(const std::string& targetAet,
-                                 const DicomMap& findResult)
-  {
-    if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL))
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent();
-    ResourceType level = StringToResourceType(tmp.c_str());
-
-    Move(targetAet, level, findResult);
-  }
-
-
-  void DicomUserConnection::MovePatient(const std::string& targetAet,
-                                        const std::string& patientId)
-  {
-    DicomMap query;
-    query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false);
-    MoveInternal(targetAet, ResourceType_Patient, query);
-  }
-
-  void DicomUserConnection::MoveStudy(const std::string& targetAet,
-                                      const std::string& studyUid)
-  {
-    DicomMap query;
-    query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false);
-    MoveInternal(targetAet, ResourceType_Study, query);
-  }
-
-  void DicomUserConnection::MoveSeries(const std::string& targetAet,
-                                       const std::string& studyUid,
-                                       const std::string& seriesUid)
-  {
-    DicomMap query;
-    query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false);
-    query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false);
-    MoveInternal(targetAet, ResourceType_Series, query);
-  }
-
-  void DicomUserConnection::MoveInstance(const std::string& targetAet,
-                                         const std::string& studyUid,
-                                         const std::string& seriesUid,
-                                         const std::string& instanceUid)
-  {
-    DicomMap query;
-    query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false);
-    query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false);
-    query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false);
-    MoveInternal(targetAet, ResourceType_Instance, query);
-  }
-
-
-  void DicomUserConnection::SetTimeout(uint32_t seconds)
-  {
-    if (seconds == 0)
-    {
-      DisableTimeout();
-    }
-    else
-    {
-      dcmConnectionTimeout.set(seconds);
-      pimpl_->dimseTimeout_ = seconds;
-      pimpl_->acseTimeout_ = 10;  // Timeout used during association negociation
-    }
-  }
-
-
-  void DicomUserConnection::DisableTimeout()
-  {
-    /**
-     * Global timeout (seconds) for connecting to remote hosts.
-     * Default value is -1 which selects infinite timeout, i.e. blocking connect().
-     */
-    dcmConnectionTimeout.set(-1);
-    pimpl_->dimseTimeout_ = 0;
-    pimpl_->acseTimeout_ = 10;  // Timeout used during association negociation
-  }
-
-
-  void DicomUserConnection::CheckStorageSOPClassesInvariant() const
-  {
-    assert(storageSOPClasses_.size() + 
-           defaultStorageSOPClasses_.size() + 
-           reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES);
-  }
-
-  void DicomUserConnection::AddStorageSOPClass(const char* sop)
-  {
-    CheckStorageSOPClassesInvariant();
-
-    if (storageSOPClasses_.find(sop) != storageSOPClasses_.end())
-    {
-      // This storage SOP class is already explicitly registered. Do
-      // nothing.
-      return;
-    }
-
-    if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end())
-    {
-      // This storage SOP class is not explicitly registered, but is
-      // used by default. Just register it explicitly.
-      defaultStorageSOPClasses_.erase(sop);
-      storageSOPClasses_.insert(sop);
-
-      CheckStorageSOPClassesInvariant();
-      return;
-    }
-
-    // This storage SOP class is neither explicitly, nor implicitly
-    // registered. Close the connection and register it explicitly.
-
-    Close();
-
-    if (reservedStorageSOPClasses_.size() + 
-        storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)  // (*)
-    {
-      // The maximum number of SOP classes is reached
-      ResetStorageSOPClasses();
-      defaultStorageSOPClasses_.erase(sop);
-    }
-    else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + 
-             defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)
-    {
-      // Make room in the default storage syntaxes
-      assert(!defaultStorageSOPClasses_.empty());  // Necessarily true because condition (*) is false
-      defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin());
-    }
-
-    // Explicitly register the new storage syntax
-    storageSOPClasses_.insert(sop);
-
-    CheckStorageSOPClassesInvariant();
-  }
-
-
-  void DicomUserConnection::FindWorklist(DicomFindAnswers& result,
-                                         ParsedDicomFile& query)
-  {
-    CheckIsOpen();
-
-    DcmDataset* dataset = query.GetDcmtkObject().getDataset();
-    const char* sopClass = UID_FINDModalityWorklistInformationModel;
-
-    ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true, NULL, pimpl_->dimseTimeout_);
-  }
-
-  
-  void DicomUserConnection::SetDefaultTimeout(uint32_t seconds)
-  {
-    LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " 
-              << seconds << " seconds (0 = no timeout)";
-    defaultTimeout_ = seconds;
-  }  
-}
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,205 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1
-#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1
-#endif
-
-#include "DicomFindAnswers.h"
-#include "../ServerEnumerations.h"
-#include "RemoteModalityParameters.h"
-
-#include <stdint.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/noncopyable.hpp>
-#include <list>
-
-namespace Orthanc
-{
-  class DicomUserConnection : public boost::noncopyable
-  {
-  private:
-    struct PImpl;
-    boost::shared_ptr<PImpl> pimpl_;
-
-    // Connection parameters
-    std::string preferredTransferSyntax_;
-    std::string localAet_;
-    std::string remoteAet_;
-    std::string remoteHost_;
-    uint16_t remotePort_;
-    ModalityManufacturer manufacturer_;
-    std::set<std::string> storageSOPClasses_;
-    std::list<std::string> reservedStorageSOPClasses_;
-    std::set<std::string> defaultStorageSOPClasses_;
-
-    void CheckIsOpen() const;
-
-    void SetupPresentationContexts(const std::string& preferredTransferSyntax);
-
-    void MoveInternal(const std::string& targetAet,
-                      ResourceType level,
-                      const DicomMap& fields);
-
-    void ResetStorageSOPClasses();
-
-    void CheckStorageSOPClassesInvariant() const;
-
-  public:
-    DicomUserConnection();
-
-    ~DicomUserConnection();
-
-    void SetRemoteModality(const RemoteModalityParameters& parameters);
-
-    void SetLocalApplicationEntityTitle(const std::string& aet);
-
-    const std::string& GetLocalApplicationEntityTitle() const
-    {
-      return localAet_;
-    }
-
-    void SetRemoteApplicationEntityTitle(const std::string& aet);
-
-    const std::string& GetRemoteApplicationEntityTitle() const
-    {
-      return remoteAet_;
-    }
-
-    void SetRemoteHost(const std::string& host);
-
-    const std::string& GetRemoteHost() const
-    {
-      return remoteHost_;
-    }
-
-    void SetRemotePort(uint16_t port);
-
-    uint16_t GetRemotePort() const
-    {
-      return remotePort_;
-    }
-
-    void SetRemoteManufacturer(ModalityManufacturer manufacturer);
-
-    ModalityManufacturer GetRemoteManufacturer() const
-    {
-      return manufacturer_;
-    }
-
-    void ResetPreferredTransferSyntax();
-
-    void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax);
-
-    const std::string& GetPreferredTransferSyntax() const
-    {
-      return preferredTransferSyntax_;
-    }
-
-    void AddStorageSOPClass(const char* sop);
-
-    void Open();
-
-    void Close();
-
-    bool IsOpen() const;
-
-    bool Echo();
-
-    void Store(const char* buffer, 
-               size_t size,
-               const std::string& moveOriginatorAET,
-               uint16_t moveOriginatorID);
-
-    void Store(const char* buffer, 
-               size_t size)
-    {
-      Store(buffer, size, "", 0);  // Not a C-Move
-    }
-
-    void Store(const std::string& buffer,
-               const std::string& moveOriginatorAET,
-               uint16_t moveOriginatorID);
-
-    void Store(const std::string& buffer)
-    {
-      Store(buffer, "", 0);  // Not a C-Move
-    }
-
-    void StoreFile(const std::string& path,
-                   const std::string& moveOriginatorAET,
-                   uint16_t moveOriginatorID);
-
-    void StoreFile(const std::string& path)
-    {
-      StoreFile(path, "", 0);  // Not a C-Move
-    }
-
-    void Find(DicomFindAnswers& result,
-              ResourceType level,
-              const DicomMap& fields);
-
-    void Move(const std::string& targetAet,
-              ResourceType level,
-              const DicomMap& findResult);
-
-    void Move(const std::string& targetAet,
-              const DicomMap& findResult);
-
-    void MovePatient(const std::string& targetAet,
-                     const std::string& patientId);
-
-    void MoveStudy(const std::string& targetAet,
-                   const std::string& studyUid);
-
-    void MoveSeries(const std::string& targetAet,
-                    const std::string& studyUid,
-                    const std::string& seriesUid);
-
-    void MoveInstance(const std::string& targetAet,
-                      const std::string& studyUid,
-                      const std::string& seriesUid,
-                      const std::string& instanceUid);
-
-    void SetTimeout(uint32_t seconds);
-
-    void DisableTimeout();
-
-    void FindWorklist(DicomFindAnswers& result,
-                      ParsedDicomFile& query);
-
-    static void SetDefaultTimeout(uint32_t seconds);
-  };
-}
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../ServerEnumerations.h"
-
-#include <string>
-
-namespace Orthanc
-{
-  class IApplicationEntityFilter : public boost::noncopyable
-  {
-  public:
-    virtual ~IApplicationEntityFilter()
-    {
-    }
-
-    virtual bool IsAllowedConnection(const std::string& remoteIp,
-                                     const std::string& remoteAet,
-                                     const std::string& calledAet) = 0;
-
-    virtual bool IsAllowedRequest(const std::string& remoteIp,
-                                  const std::string& remoteAet,
-                                  const std::string& calledAet,
-                                  DicomRequestType type) = 0;
-
-    virtual bool IsAllowedTransferSyntax(const std::string& remoteIp,
-                                         const std::string& remoteAet,
-                                         const std::string& calledAet,
-                                         TransferSyntax syntax) = 0;
-
-    virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
-                                           const std::string& remoteAet,
-                                           const std::string& calledAet) = 0;
-  };
-}
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "DicomFindAnswers.h"
-
-namespace Orthanc
-{
-  class IFindRequestHandler : public boost::noncopyable
-  {
-  public:
-    virtual ~IFindRequestHandler()
-    {
-    }
-
-    virtual void Handle(DicomFindAnswers& answers,
-                        const DicomMap& input,
-                        const std::list<DicomTag>& sequencesToReturn,
-                        const std::string& remoteIp,
-                        const std::string& remoteAet,
-                        const std::string& calledAet,
-                        ModalityManufacturer manufacturer) = 0;
-  };
-}
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IFindRequestHandler.h"
-
-namespace Orthanc
-{
-  class IFindRequestHandlerFactory : public boost::noncopyable
-  {
-  public:
-    virtual ~IFindRequestHandlerFactory()
-    {
-    }
-
-    virtual IFindRequestHandler* ConstructFindRequestHandler() = 0;
-  };
-}
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../../Core/DicomFormat/DicomMap.h"
-
-#include <vector>
-#include <string>
-
-
-namespace Orthanc
-{
-  class IMoveRequestIterator : public boost::noncopyable
-  {
-  public:
-    enum Status
-    {
-      Status_Success,
-      Status_Failure,
-      Status_Warning
-    };
-
-    virtual ~IMoveRequestIterator()
-    {
-    }
-
-    virtual unsigned int GetSubOperationCount() const = 0;
-
-    virtual Status DoNext() = 0;
-  };
-
-
-  class IMoveRequestHandler
-  {
-  public:
-    virtual ~IMoveRequestHandler()
-    {
-    }
-
-    virtual IMoveRequestIterator* Handle(const std::string& targetAet,
-                                         const DicomMap& input,
-                                         const std::string& originatorIp,
-                                         const std::string& originatorAet,
-                                         const std::string& calledAet,
-                                         uint16_t originatorId) = 0;
-  };
-
-}
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IMoveRequestHandler.h"
-
-namespace Orthanc
-{
-  class IMoveRequestHandlerFactory : public boost::noncopyable
-  {
-  public:
-    virtual ~IMoveRequestHandlerFactory()
-    {
-    }
-
-    virtual IMoveRequestHandler* ConstructMoveRequestHandler() = 0;
-  };
-}
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../../Core/DicomFormat/DicomMap.h"
-
-#include <vector>
-#include <string>
-#include <json/json.h>
-
-namespace Orthanc
-{
-  class IStoreRequestHandler : public boost::noncopyable
-  {
-  public:
-    virtual ~IStoreRequestHandler()
-    {
-    }
-
-    virtual void Handle(const std::string& dicomFile,
-                        const DicomMap& dicomSummary,
-                        const Json::Value& dicomJson,
-                        const std::string& remoteIp,
-                        const std::string& remoteAet,
-                        const std::string& calledAet) = 0;
-  };
-}
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IStoreRequestHandler.h"
-
-namespace Orthanc
-{
-  class IStoreRequestHandlerFactory : public boost::noncopyable
-  {
-  public:
-    virtual ~IStoreRequestHandlerFactory()
-    {
-    }
-
-    virtual IStoreRequestHandler* ConstructStoreRequestHandler() = 0;
-  };
-}
--- a/OrthancServer/DicomProtocol/IWorklistRequestHandler.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "DicomFindAnswers.h"
-
-namespace Orthanc
-{
-  class IWorklistRequestHandler : public boost::noncopyable
-  {
-  public:
-    virtual ~IWorklistRequestHandler()
-    {
-    }
-
-    virtual void Handle(DicomFindAnswers& answers,
-                        ParsedDicomFile& query,
-                        const std::string& remoteIp,
-                        const std::string& remoteAet,
-                        const std::string& calledAet,
-                        ModalityManufacturer manufacturer) = 0;
-  };
-}
--- a/OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "IWorklistRequestHandler.h"
-
-namespace Orthanc
-{
-  class IWorklistRequestHandlerFactory : public boost::noncopyable
-  {
-  public:
-    virtual ~IWorklistRequestHandlerFactory()
-    {
-    }
-
-    virtual IWorklistRequestHandler* ConstructWorklistRequestHandler() = 0;
-  };
-}
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * 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/Logging.h"
-#include "../../Core/OrthancException.h"
-
-#include <boost/lexical_cast.hpp>
-#include <stdexcept>
-
-namespace Orthanc
-{
-  RemoteModalityParameters::RemoteModalityParameters() :
-    aet_("ORTHANC"),
-    host_("127.0.0.1"),
-    port_(104),
-    manufacturer_(ModalityManufacturer_Generic)
-  {
-  }
-
-  RemoteModalityParameters::RemoteModalityParameters(const std::string& aet,
-                                                     const std::string& host,
-                                                     uint16_t port,
-                                                     ModalityManufacturer manufacturer)
-  {
-    SetApplicationEntityTitle(aet);
-    SetHost(host);
-    SetPort(port);
-    SetManufacturer(manufacturer);
-  }
-
-
-  void RemoteModalityParameters::FromJson(const Json::Value& modality)
-  {
-    if (!modality.isArray() ||
-        (modality.size() != 3 && modality.size() != 4))
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    SetApplicationEntityTitle(modality.get(0u, "").asString());
-    SetHost(modality.get(1u, "").asString());
-
-    const Json::Value& portValue = modality.get(2u, "");
-    try
-    {
-      int tmp = portValue.asInt();
-
-      if (tmp <= 0 || tmp >= 65535)
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-
-      SetPort(static_cast<uint16_t>(tmp));
-    }
-    catch (std::runtime_error /* error inside JsonCpp */)
-    {
-      try
-      {
-        SetPort(boost::lexical_cast<uint16_t>(portValue.asString()));
-      }
-      catch (boost::bad_lexical_cast)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-    }
-
-    if (modality.size() == 4)
-    {
-      const std::string& manufacturer = modality.get(3u, "").asString();
-
-      try
-      {
-        SetManufacturer(manufacturer);
-      }
-      catch (OrthancException&)
-      {
-        LOG(ERROR) << "Unknown modality manufacturer: \"" << manufacturer << "\"";
-        throw;
-      }
-    }
-    else
-    {
-      SetManufacturer(ModalityManufacturer_Generic);
-    }
-  }
-
-  void RemoteModalityParameters::ToJson(Json::Value& value) const
-  {
-    value = Json::arrayValue;
-    value.append(GetApplicationEntityTitle());
-    value.append(GetHost());
-    value.append(GetPort());
-    value.append(EnumerationToString(GetManufacturer()));
-  }
-}
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../ServerEnumerations.h"
-
-#include <stdint.h>
-#include <string>
-#include <json/json.h>
-
-namespace Orthanc
-{
-  class RemoteModalityParameters
-  {
-  private:
-    std::string aet_;
-    std::string host_;
-    uint16_t port_;
-    ModalityManufacturer manufacturer_;
-
-  public:
-    RemoteModalityParameters();
-
-    RemoteModalityParameters(const std::string& aet,
-                             const std::string& host,
-                             uint16_t port,
-                             ModalityManufacturer manufacturer);
-
-    const std::string& GetApplicationEntityTitle() const
-    {
-      return aet_;
-    }
-
-    void SetApplicationEntityTitle(const std::string& aet)
-    {
-      aet_ = aet;
-    }
-
-    const std::string& GetHost() const
-    {
-      return host_;
-    }
-
-    void SetHost(const std::string& host)
-    {
-      host_ = host;
-    }
-    
-    uint16_t GetPort() const
-    {
-      return port_;
-    }
-
-    void SetPort(uint16_t port)
-    {
-      port_ = port;
-    }
-
-    ModalityManufacturer GetManufacturer() const
-    {
-      return manufacturer_;
-    }
-
-    void SetManufacturer(ModalityManufacturer manufacturer)
-    {
-      manufacturer_ = manufacturer;
-    }    
-
-    void SetManufacturer(const std::string& manufacturer)
-    {
-      manufacturer_ = StringToModalityManufacturer(manufacturer);
-    }
-
-    void FromJson(const Json::Value& modality);
-
-    void ToJson(Json::Value& value) const;
-  };
-}
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "ReusableDicomUserConnection.h"
-
-#include "../../Core/Logging.h"
-#include "../../Core/OrthancException.h"
-
-namespace Orthanc
-{
-  static boost::posix_time::ptime Now()
-  {
-    return boost::posix_time::microsec_clock::local_time();
-  }
-
-  void ReusableDicomUserConnection::Open(const std::string& localAet,
-                                         const RemoteModalityParameters& remote)
-  {
-    if (connection_ != NULL &&
-        connection_->GetLocalApplicationEntityTitle() == localAet &&
-        connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() &&
-        connection_->GetRemoteHost() == remote.GetHost() &&
-        connection_->GetRemotePort() == remote.GetPort() &&
-        connection_->GetRemoteManufacturer() == remote.GetManufacturer())
-    {
-      // The current connection can be reused
-      LOG(INFO) << "Reusing the previous SCU connection";
-      return;
-    }
-
-    Close();
-
-    connection_ = new DicomUserConnection();
-    connection_->SetLocalApplicationEntityTitle(localAet);
-    connection_->SetRemoteModality(remote);
-    connection_->Open();
-  }
-    
-  void ReusableDicomUserConnection::Close()
-  {
-    if (connection_ != NULL)
-    {
-      delete connection_;
-      connection_ = NULL;
-    }
-  }
-
-  void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that)
-  {
-    for (;;)
-    {
-      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-      if (!that->continue_)
-      {
-        //LOG(INFO) << "Finishing the thread watching the global SCU connection";
-        return;
-      }
-
-      {
-        boost::mutex::scoped_lock lock(that->mutex_);
-        if (that->connection_ != NULL &&
-            Now() >= that->lastUse_ + that->timeBeforeClose_)
-        {
-          LOG(INFO) << "Closing the global SCU connection after timeout";
-          that->Close();
-        }
-      }
-    }
-  }
-    
-
-  ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that,
-                                              const std::string& localAet,
-                                              const RemoteModalityParameters& remote) :
-    ::Orthanc::Locker(that)
-  {
-    that.Open(localAet, remote);
-    connection_ = that.connection_;    
-  }
-
-
-  DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection()
-  {
-    if (connection_ == NULL)
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    return *connection_;
-  }      
-
-  ReusableDicomUserConnection::ReusableDicomUserConnection() : 
-    connection_(NULL), 
-    timeBeforeClose_(boost::posix_time::seconds(5))  // By default, close connection after 5 seconds
-  {
-    lastUse_ = Now();
-    continue_ = true;
-    closeThread_ = boost::thread(CloseThread, this);
-  }
-
-  ReusableDicomUserConnection::~ReusableDicomUserConnection()
-  {
-    if (continue_)
-    {
-      LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!";
-      Finalize();
-    }
-  }
-
-  void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (ms == 0)
-    {
-      ms = 1;
-    }
-
-    timeBeforeClose_ = boost::posix_time::milliseconds(ms);
-  }
-
-  void ReusableDicomUserConnection::Lock()
-  {
-    mutex_.lock();
-  }
-
-  void ReusableDicomUserConnection::Unlock()
-  {
-    if (connection_ != NULL &&
-        connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp)
-    {
-      // "storescp" from DCMTK has problems when reusing a
-      // connection. Always close.
-      Close();
-    }
-
-    lastUse_ = Now();
-    mutex_.unlock();
-  }
-
-  
-  void ReusableDicomUserConnection::Finalize()
-  {
-    if (continue_)
-    {
-      continue_ = false;
-
-      if (closeThread_.joinable())
-      {
-        closeThread_.join();
-      }
-
-      Close();
-    }
-  }
-}
-
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "DicomUserConnection.h"
-#include "../../Core/MultiThreading/Locker.h"
-
-#include <boost/thread.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
-
-namespace Orthanc
-{
-  class ReusableDicomUserConnection : public ILockable
-  {
-  private:
-    boost::mutex mutex_;
-    DicomUserConnection* connection_;
-    bool continue_;
-    boost::posix_time::time_duration timeBeforeClose_;
-    boost::posix_time::ptime lastUse_;
-    boost::thread closeThread_;
-
-    void Open(const std::string& localAet,
-              const RemoteModalityParameters& remote);
-    
-    void Close();
-
-    static void CloseThread(ReusableDicomUserConnection* that);
-
-  protected:
-    virtual void Lock();
-
-    virtual void Unlock();
-    
-  public:
-    class Locker : public ::Orthanc::Locker
-    {
-    private:
-      DicomUserConnection* connection_;
-
-    public:
-      Locker(ReusableDicomUserConnection& that,
-             const std::string& localAet,
-             const RemoteModalityParameters& remote);
-
-      DicomUserConnection& GetConnection();
-    };
-
-    ReusableDicomUserConnection();
-
-    virtual ~ReusableDicomUserConnection();
-
-    void SetMillisecondsBeforeClose(uint64_t ms);
-
-    void Finalize();
-  };
-}
-
--- a/OrthancServer/FromDcmtkBridge.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2075 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeadersServer.h"
-
-#ifndef NOMINMAX
-#define NOMINMAX
-#endif
-
-#include "FromDcmtkBridge.h"
-#include "ToDcmtkBridge.h"
-#include "../Core/Logging.h"
-#include "../Core/SystemToolbox.h"
-#include "../Core/Toolbox.h"
-#include "../Core/TemporaryFile.h"
-#include "../Core/OrthancException.h"
-
-#include <list>
-#include <limits>
-
-#include <boost/lexical_cast.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/algorithm/string/predicate.hpp>
-
-#include <dcmtk/dcmdata/dcdeftag.h>
-#include <dcmtk/dcmdata/dcdicent.h>
-#include <dcmtk/dcmdata/dcdict.h>
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <dcmtk/dcmdata/dcostrmb.h>
-#include <dcmtk/dcmdata/dcpixel.h>
-#include <dcmtk/dcmdata/dcuid.h>
-#include <dcmtk/dcmdata/dcistrmb.h>
-
-#include <dcmtk/dcmdata/dcvrae.h>
-#include <dcmtk/dcmdata/dcvras.h>
-#include <dcmtk/dcmdata/dcvrat.h>
-#include <dcmtk/dcmdata/dcvrcs.h>
-#include <dcmtk/dcmdata/dcvrda.h>
-#include <dcmtk/dcmdata/dcvrds.h>
-#include <dcmtk/dcmdata/dcvrdt.h>
-#include <dcmtk/dcmdata/dcvrfd.h>
-#include <dcmtk/dcmdata/dcvrfl.h>
-#include <dcmtk/dcmdata/dcvris.h>
-#include <dcmtk/dcmdata/dcvrlo.h>
-#include <dcmtk/dcmdata/dcvrlt.h>
-#include <dcmtk/dcmdata/dcvrpn.h>
-#include <dcmtk/dcmdata/dcvrsh.h>
-#include <dcmtk/dcmdata/dcvrsl.h>
-#include <dcmtk/dcmdata/dcvrss.h>
-#include <dcmtk/dcmdata/dcvrst.h>
-#include <dcmtk/dcmdata/dcvrtm.h>
-#include <dcmtk/dcmdata/dcvrui.h>
-#include <dcmtk/dcmdata/dcvrul.h>
-#include <dcmtk/dcmdata/dcvrus.h>
-#include <dcmtk/dcmdata/dcvrut.h>
-
-#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
-#  include <EmbeddedResources.h>
-#endif
-
-#if ORTHANC_ENABLE_DCMTK_JPEG == 1
-#  include <dcmtk/dcmjpeg/djdecode.h>
-#endif
-
-#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
-#  include <dcmtk/dcmjpls/djdecode.h>
-#endif
-
-
-namespace Orthanc
-{
-  static inline uint16_t GetCharValue(char c)
-  {
-    if (c >= '0' && c <= '9')
-      return c - '0';
-    else if (c >= 'a' && c <= 'f')
-      return c - 'a' + 10;
-    else if (c >= 'A' && c <= 'F')
-      return c - 'A' + 10;
-    else
-      return 0;
-  }
-
-  static inline uint16_t GetTagValue(const char* c)
-  {
-    return ((GetCharValue(c[0]) << 12) + 
-            (GetCharValue(c[1]) << 8) + 
-            (GetCharValue(c[2]) << 4) + 
-            GetCharValue(c[3]));
-  }
-
-
-#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
-  static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary,
-                                     EmbeddedResources::FileResourceId resource)
-  {
-    std::string content;
-    EmbeddedResources::GetFileResource(content, resource);
-
-    TemporaryFile tmp;
-    tmp.Write(content);
-
-    if (!dictionary.loadDictionary(tmp.GetPath().c_str()))
-    {
-      LOG(ERROR) << "Cannot read embedded dictionary. Under Windows, make sure that " 
-                 << "your TEMP directory does not contain special characters.";
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-                             
-#else
-  static void LoadExternalDictionary(DcmDataDictionary& dictionary,
-                                     const std::string& directory,
-                                     const std::string& filename)
-  {
-    boost::filesystem::path p = directory;
-    p = p / filename;
-
-    LOG(WARNING) << "Loading the external DICOM dictionary " << p;
-
-    if (!dictionary.loadDictionary(p.string().c_str()))
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-#endif
-
-
-  namespace
-  {
-    class DictionaryLocker
-    {
-    private:
-      DcmDataDictionary& dictionary_;
-
-    public:
-      DictionaryLocker() : dictionary_(dcmDataDict.wrlock())
-      {
-      }
-
-      ~DictionaryLocker()
-      {
-        dcmDataDict.unlock();
-      }
-
-      DcmDataDictionary& operator*()
-      {
-        return dictionary_;
-      }
-
-      DcmDataDictionary* operator->()
-      {
-        return &dictionary_;
-      }
-    };
-  }
-
-
-  void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary)
-  {
-    LOG(INFO) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER;
-    
-    {
-      DictionaryLocker locker;
-
-      locker->clear();
-
-#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
-      LOG(WARNING) << "Loading the embedded dictionaries";
-      /**
-       * Do not load DICONDE dictionary, it breaks the other tags. The
-       * command "strace storescu 2>&1 |grep dic" shows that DICONDE
-       * dictionary is not loaded by storescu.
-       **/
-      //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE);
-
-      LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM);
-
-      if (loadPrivateDictionary)
-      {
-        LOG(INFO) << "Loading the embedded dictionary of private tags";
-        LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE);
-      }
-      else
-      {
-        LOG(INFO) << "The dictionary of private tags has not been loaded";
-      }
-
-#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__)
-      std::string path = DCMTK_DICTIONARY_DIR;
-
-      const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
-      if (env != NULL)
-      {
-        path = std::string(env);
-      }
-
-      LoadExternalDictionary(*locker, path, "dicom.dic");
-
-      if (loadPrivateDictionary)
-      {
-        LoadExternalDictionary(*locker, path, "private.dic");
-      }
-      else
-      {
-        LOG(INFO) << "The dictionary of private tags has not been loaded";
-      }
-
-#else
-#error Support your platform here
-#endif
-    }
-
-    /* make sure data dictionary is loaded */
-    if (!dcmDataDict.isDictionaryLoaded())
-    {
-      LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE;
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    {
-      // Test the dictionary with a simple DICOM tag
-      DcmTag key(0x0010, 0x1030); // This is PatientWeight
-      if (key.getEVR() != EVR_DS)
-      {
-        LOG(ERROR) << "The DICOM dictionary has not been correctly read";
-        throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-  }
-
-
-  void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag,
-                                              ValueRepresentation vr,
-                                              const std::string& name,
-                                              unsigned int minMultiplicity,
-                                              unsigned int maxMultiplicity,
-                                              const std::string& privateCreator)
-  {
-    if (minMultiplicity < 1)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    bool arbitrary = false;
-    if (maxMultiplicity == 0)
-    {
-      maxMultiplicity = DcmVariableVM;
-      arbitrary = true;
-    }
-    else if (maxMultiplicity < minMultiplicity)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-    
-    DcmEVR evr = ToDcmtkBridge::Convert(vr);
-
-    LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(evr).getValidVRName()) << " " 
-              << name << " (multiplicity: " << minMultiplicity << "-" 
-              << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")";
-
-    std::auto_ptr<DcmDictEntry>  entry;
-    if (privateCreator.empty())
-    {
-      if (tag.GetGroup() % 2 == 1)
-      {
-        char buf[128];
-        sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), "
-                "but no private creator was associated with it", 
-                tag.GetGroup(), tag.GetElement());
-        LOG(WARNING) << buf;
-      }
-
-      entry.reset(new DcmDictEntry(tag.GetGroup(),
-                                   tag.GetElement(),
-                                   evr, name.c_str(),
-                                   static_cast<int>(minMultiplicity),
-                                   static_cast<int>(maxMultiplicity),
-                                   NULL    /* version */,
-                                   OFTrue  /* doCopyString */,
-                                   NULL    /* private creator */));
-    }
-    else
-    {
-      // "Private Data Elements have an odd Group Number that is not
-      // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or
-      // (FFFF,eeee)."
-      if (tag.GetGroup() % 2 == 0 /* even */ ||
-          tag.GetGroup() == 0x0001 ||
-          tag.GetGroup() == 0x0003 ||
-          tag.GetGroup() == 0x0005 ||
-          tag.GetGroup() == 0x0007 ||
-          tag.GetGroup() == 0xffff)
-      {
-        char buf[128];
-        sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009",
-                tag.GetGroup(), tag.GetElement());
-        LOG(ERROR) << buf;
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-
-      entry.reset(new DcmDictEntry(tag.GetGroup(),
-                                   tag.GetElement(),
-                                   evr, name.c_str(),
-                                   static_cast<int>(minMultiplicity),
-                                   static_cast<int>(maxMultiplicity),
-                                   "private" /* version */,
-                                   OFTrue    /* doCopyString */,
-                                   privateCreator.c_str()));
-    }
-
-    entry->setGroupRangeRestriction(DcmDictRange_Unspecified);
-    entry->setElementRangeRestriction(DcmDictRange_Unspecified);
-
-    {
-      DictionaryLocker locker;
-
-      if (locker->findEntry(name.c_str()))
-      {
-        LOG(ERROR) << "Cannot register two tags with the same symbolic name \"" << name << "\"";
-        throw OrthancException(ErrorCode_AlreadyExistingTag);
-      }
-
-      locker->addEntry(entry.release());
-    }
-  }
-
-
-  Encoding FromDcmtkBridge::DetectEncoding(DcmItem& dataset,
-                                           Encoding defaultEncoding)
-  {
-    Encoding encoding = defaultEncoding;
-
-    OFString tmp;
-    if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good())
-    {
-      std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str()));
-
-      if (characterSet.empty())
-      {
-        // Empty specific character set tag: Use the default encoding
-      }
-      else if (GetDicomEncoding(encoding, characterSet.c_str()))
-      {
-        // The specific character set is supported by the Orthanc core
-      }
-      else
-      {
-        LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet
-                     << ", fallback to ASCII (remove all special characters)";
-        encoding = Encoding_Ascii;
-      }
-    }
-    else
-    {
-      // No specific character set tag: Use the default encoding
-    }
-
-    return encoding;
-  }
-
-
-  void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, 
-                                            DcmItem& dataset,
-                                            unsigned int maxStringLength,
-                                            Encoding defaultEncoding)
-  {
-    Encoding encoding = DetectEncoding(dataset, defaultEncoding);
-
-    target.Clear();
-    for (unsigned long i = 0; i < dataset.card(); i++)
-    {
-      DcmElement* element = dataset.getElement(i);
-      if (element && element->isLeaf())
-      {
-        target.SetValue(element->getTag().getGTag(),
-                        element->getTag().getETag(),
-                        ConvertLeafElement(*element, DicomToJsonFlags_Default, maxStringLength, encoding));
-      }
-    }
-  }
-
-
-  DicomTag FromDcmtkBridge::Convert(const DcmTag& tag)
-  {
-    return DicomTag(tag.getGTag(), tag.getETag());
-  }
-
-
-  DicomTag FromDcmtkBridge::GetTag(const DcmElement& element)
-  {
-    return DicomTag(element.getGTag(), element.getETag());
-  }
-
-
-  DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element,
-                                                  DicomToJsonFlags flags,
-                                                  unsigned int maxStringLength,
-                                                  Encoding encoding)
-  {
-    if (!element.isLeaf())
-    {
-      // This function is only applicable to leaf elements
-      throw OrthancException(ErrorCode_BadParameterType);
-    }
-
-    char *c = NULL;
-    if (element.isaString() &&
-        element.getString(c).good())
-    {
-      if (c == NULL)  // This case corresponds to the empty string
-      {
-        return new DicomValue("", false);
-      }
-      else
-      {
-        std::string s(c);
-        std::string utf8 = Toolbox::ConvertToUtf8(s, encoding);
-
-        if (maxStringLength != 0 &&
-            utf8.size() > maxStringLength)
-        {
-          return new DicomValue;  // Too long, create a NULL value
-        }
-        else
-        {
-          return new DicomValue(utf8, false);
-        }
-      }
-    }
-
-
-    if (element.getVR() == EVR_UN)
-    {
-      // Unknown value representation: Lookup in the dictionary. This
-      // is notably the case for private tags registered with the
-      // "Dictionary" configuration option.
-      DictionaryLocker locker;
-      
-      const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), 
-                                                    element.getTag().getPrivateCreator());
-      if (entry != NULL && 
-          entry->getVR().isaString())
-      {
-        Uint8* data = NULL;
-
-        // At (*), we do not try and convert to UTF-8, as nothing says
-        // the encoding of the private tag is the same as that of the
-        // remaining of the DICOM dataset. Only go for ASCII strings.
-
-        if (element.getUint8Array(data) == EC_Normal &&
-            Toolbox::IsAsciiString(data, element.getLength()))   // (*)
-        {
-          if (data == NULL)
-          {
-            return new DicomValue("", false);   // Empty string
-          }
-          else if (maxStringLength != 0 &&
-                   element.getLength() > maxStringLength)
-          {
-            return new DicomValue;  // Too long, create a NULL value
-          }
-          else
-          {
-            std::string s(reinterpret_cast<const char*>(data), element.getLength());
-            return new DicomValue(s, false);
-          }
-        }
-      }
-    }
-
-
-    try
-    {
-      // http://support.dcmtk.org/docs/dcvr_8h-source.html
-      switch (element.getVR())
-      {
-
-        /**
-         * Deal with binary data (including PixelData).
-         **/
-
-        case EVR_OB:  // other byte
-        case EVR_OF:  // other float
-        case EVR_OW:  // other word
-        case EVR_UN:  // unknown value representation
-        case EVR_ox:  // OB or OW depending on context
-        case EVR_DS:  // decimal string
-        case EVR_IS:  // integer string
-        case EVR_AS:  // age string
-        case EVR_DA:  // date string
-        case EVR_DT:  // date time string
-        case EVR_TM:  // time string
-        case EVR_AE:  // application entity title
-        case EVR_CS:  // code string
-        case EVR_SH:  // short string
-        case EVR_LO:  // long string
-        case EVR_ST:  // short text
-        case EVR_LT:  // long text
-        case EVR_UT:  // unlimited text
-        case EVR_PN:  // person name
-        case EVR_UI:  // unique identifier
-        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
-        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
-        {
-          if (!(flags & DicomToJsonFlags_ConvertBinaryToNull))
-          {
-            Uint8* data = NULL;
-            if (element.getUint8Array(data) == EC_Normal)
-            {
-              return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true);
-            }
-          }
-
-          return new DicomValue;
-        }
-    
-        /**
-         * Numeric types
-         **/ 
-      
-        case EVR_SL:  // signed long
-        {
-          Sint32 f;
-          if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good())
-            return new DicomValue(boost::lexical_cast<std::string>(f), false);
-          else
-            return new DicomValue;
-        }
-
-        case EVR_SS:  // signed short
-        {
-          Sint16 f;
-          if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good())
-            return new DicomValue(boost::lexical_cast<std::string>(f), false);
-          else
-            return new DicomValue;
-        }
-
-        case EVR_UL:  // unsigned long
-        {
-          Uint32 f;
-          if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good())
-            return new DicomValue(boost::lexical_cast<std::string>(f), false);
-          else
-            return new DicomValue;
-        }
-
-        case EVR_US:  // unsigned short
-        {
-          Uint16 f;
-          if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good())
-            return new DicomValue(boost::lexical_cast<std::string>(f), false);
-          else
-            return new DicomValue;
-        }
-
-        case EVR_FL:  // float single-precision
-        {
-          Float32 f;
-          if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good())
-            return new DicomValue(boost::lexical_cast<std::string>(f), false);
-          else
-            return new DicomValue;
-        }
-
-        case EVR_FD:  // float double-precision
-        {
-          Float64 f;
-          if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good())
-            return new DicomValue(boost::lexical_cast<std::string>(f), false);
-          else
-            return new DicomValue;
-        }
-
-
-        /**
-         * Attribute tag.
-         **/
-
-        case EVR_AT:
-        {
-          DcmTagKey tag;
-          if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good())
-          {
-            DicomTag t(tag.getGroup(), tag.getElement());
-            return new DicomValue(t.Format(), false);
-          }
-          else
-          {
-            return new DicomValue;
-          }
-        }
-
-
-        /**
-         * Sequence types, should never occur at this point because of
-         * "element.isLeaf()".
-         **/
-
-        case EVR_SQ:  // sequence of items
-          return new DicomValue;
-
-
-          /**
-           * Internal to DCMTK.
-           **/ 
-
-        case EVR_xs:  // SS or US depending on context
-        case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
-        case EVR_na:  // na="not applicable", for data which has no VR
-        case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
-        case EVR_item:  // used internally for items
-        case EVR_metainfo:  // used internally for meta info datasets
-        case EVR_dataset:  // used internally for datasets
-        case EVR_fileFormat:  // used internally for DICOM files
-        case EVR_dicomDir:  // used internally for DICOMDIR objects
-        case EVR_dirRecord:  // used internally for DICOMDIR records
-        case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
-        case EVR_pixelItem:  // used internally for pixel items in a compressed image
-        case EVR_PixelData:  // used internally for uncompressed pixeld data
-        case EVR_OverlayData:  // used internally for overlay data
-          return new DicomValue;
-
-
-          /**
-           * Default case.
-           **/ 
-
-        default:
-          return new DicomValue;
-      }
-    }
-    catch (boost::bad_lexical_cast)
-    {
-      return new DicomValue;
-    }
-    catch (std::bad_cast)
-    {
-      return new DicomValue;
-    }
-  }
-
-
-  static Json::Value& PrepareNode(Json::Value& parent,
-                                  DcmElement& element,
-                                  DicomToJsonFormat format)
-  {
-    assert(parent.type() == Json::objectValue);
-
-    DicomTag tag(FromDcmtkBridge::GetTag(element));
-    const std::string formattedTag = tag.Format();
-
-    if (format == DicomToJsonFormat_Short)
-    {
-      parent[formattedTag] = Json::nullValue;
-      return parent[formattedTag];
-    }
-
-    // This code gives access to the name of the private tags
-    std::string tagName = FromDcmtkBridge::GetTagName(element);
-    
-    switch (format)
-    {
-      case DicomToJsonFormat_Human:
-        parent[tagName] = Json::nullValue;
-        return parent[tagName];
-
-      case DicomToJsonFormat_Full:
-      {
-        parent[formattedTag] = Json::objectValue;
-        Json::Value& node = parent[formattedTag];
-
-        if (element.isLeaf())
-        {
-          node["Name"] = tagName;
-
-          if (element.getTag().getPrivateCreator() != NULL)
-          {
-            node["PrivateCreator"] = element.getTag().getPrivateCreator();
-          }
-
-          return node;
-        }
-        else
-        {
-          node["Name"] = tagName;
-          node["Type"] = "Sequence";
-          node["Value"] = Json::nullValue;
-          return node["Value"];
-        }
-      }
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  static void LeafValueToJson(Json::Value& target,
-                              const DicomValue& value,
-                              DicomToJsonFormat format,
-                              DicomToJsonFlags flags,
-                              unsigned int maxStringLength)
-  {
-    Json::Value* targetValue = NULL;
-    Json::Value* targetType = NULL;
-
-    switch (format)
-    {
-      case DicomToJsonFormat_Short:
-      case DicomToJsonFormat_Human:
-      {
-        assert(target.type() == Json::nullValue);
-        targetValue = &target;
-        break;
-      }      
-
-      case DicomToJsonFormat_Full:
-      {
-        assert(target.type() == Json::objectValue);
-        target["Value"] = Json::nullValue;
-        target["Type"] = Json::nullValue;
-        targetType = &target["Type"];
-        targetValue = &target["Value"];
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    assert(targetValue != NULL);
-    assert(targetValue->type() == Json::nullValue);
-    assert(targetType == NULL || targetType->type() == Json::nullValue);
-
-    if (value.IsNull())
-    {
-      if (targetType != NULL)
-      {
-        *targetType = "Null";
-      }
-    }
-    else if (value.IsBinary())
-    {
-      if (flags & DicomToJsonFlags_ConvertBinaryToAscii)
-      {
-        *targetValue = Toolbox::ConvertToAscii(value.GetContent());
-      }
-      else
-      {
-        std::string s;
-        value.FormatDataUriScheme(s);
-        *targetValue = s;
-      }
-
-      if (targetType != NULL)
-      {
-        *targetType = "Binary";
-      }
-    }
-    else if (maxStringLength == 0 ||
-             value.GetContent().size() <= maxStringLength)
-    {
-      *targetValue = value.GetContent();
-
-      if (targetType != NULL)
-      {
-        *targetType = "String";
-      }
-    }
-    else
-    {
-      if (targetType != NULL)
-      {
-        *targetType = "TooLong";
-      }
-    }
-  }                              
-
-
-  void FromDcmtkBridge::ElementToJson(Json::Value& parent,
-                                      DcmElement& element,
-                                      DicomToJsonFormat format,
-                                      DicomToJsonFlags flags,
-                                      unsigned int maxStringLength,
-                                      Encoding encoding)
-  {
-    if (parent.type() == Json::nullValue)
-    {
-      parent = Json::objectValue;
-    }
-
-    assert(parent.type() == Json::objectValue);
-    Json::Value& target = PrepareNode(parent, element, format);
-
-    if (element.isLeaf())
-    {
-      // The "0" below lets "LeafValueToJson()" take care of "TooLong" values
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, 0, encoding));
-      LeafValueToJson(target, *v, format, flags, maxStringLength);
-    }
-    else
-    {
-      assert(target.type() == Json::nullValue);
-      target = Json::arrayValue;
-
-      // "All subclasses of DcmElement except for DcmSequenceOfItems
-      // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
-      // etc. are not." The following dynamic_cast is thus OK.
-      DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);
-
-      for (unsigned long i = 0; i < sequence.card(); i++)
-      {
-        DcmItem* child = sequence.getItem(i);
-        Json::Value& v = target.append(Json::objectValue);
-        DatasetToJson(v, *child, format, flags, maxStringLength, encoding);
-      }
-    }
-  }
-
-
-  void FromDcmtkBridge::DatasetToJson(Json::Value& parent,
-                                      DcmItem& item,
-                                      DicomToJsonFormat format,
-                                      DicomToJsonFlags flags,
-                                      unsigned int maxStringLength,
-                                      Encoding encoding)
-  {
-    assert(parent.type() == Json::objectValue);
-
-    for (unsigned long i = 0; i < item.card(); i++)
-    {
-      DcmElement* element = item.getElement(i);
-      if (element == NULL)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-
-      DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
-
-      /*element->getTag().isPrivate()*/
-      if (tag.IsPrivate() &&
-          !(flags & DicomToJsonFlags_IncludePrivateTags))    
-      {
-        continue;
-      }
-
-      if (!(flags & DicomToJsonFlags_IncludeUnknownTags))
-      {
-        DictionaryLocker locker;
-        if (locker->findEntry(element->getTag(), NULL) == NULL)
-        {
-          continue;
-        }
-      }
-
-      DcmEVR evr = element->getTag().getEVR();
-      if (evr == EVR_OB ||
-          evr == EVR_OF ||
-          evr == EVR_OW ||
-          evr == EVR_UN ||
-          evr == EVR_ox)
-      {
-        // This is a binary tag
-        if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) ||
-            (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary)))
-        {
-          continue;
-        }
-      }
-
-      FromDcmtkBridge::ElementToJson(parent, *element, format, flags, maxStringLength, encoding);
-    }
-  }
-
-
-  void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, 
-                                           DcmDataset& dataset,
-                                           DicomToJsonFormat format,
-                                           DicomToJsonFlags flags,
-                                           unsigned int maxStringLength,
-                                           Encoding defaultEncoding)
-  {
-    Encoding encoding = DetectEncoding(dataset, defaultEncoding);
-
-    target = Json::objectValue;
-    DatasetToJson(target, dataset, format, flags, maxStringLength, encoding);
-  }
-
-
-  void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target, 
-                                            DcmMetaInfo& dataset,
-                                            DicomToJsonFormat format,
-                                            DicomToJsonFlags flags,
-                                            unsigned int maxStringLength)
-  {
-    target = Json::objectValue;
-    DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii);
-  }
-
-
-
-  static std::string GetTagNameInternal(DcmTag& tag)
-  {
-    {
-      // Some patches for important tags because of different DICOM
-      // dictionaries between DCMTK versions
-      DicomTag tmp(tag.getGroup(), tag.getElement());
-      std::string n = tmp.GetMainTagsName();
-      if (n.size() != 0)
-      {
-        return n;
-      }
-      // End of patches
-    }
-
-#if 0
-    // This version explicitly calls the dictionary
-    const DcmDataDictionary& dict = dcmDataDict.rdlock();
-    const DcmDictEntry* entry = dict.findEntry(tag, NULL);
-
-    std::string s(DcmTag_ERROR_TagName);
-    if (entry != NULL)
-    {
-      s = std::string(entry->getTagName());
-    }
-
-    dcmDataDict.unlock();
-    return s;
-#else
-    const char* name = tag.getTagName();
-    if (name == NULL)
-    {
-      return DcmTag_ERROR_TagName;
-    }
-    else
-    {
-      return std::string(name);
-    }
-#endif
-  }
-
-
-  std::string FromDcmtkBridge::GetTagName(const DicomTag& t,
-                                          const std::string& privateCreator)
-  {
-    DcmTag tag(t.GetGroup(), t.GetElement());
-
-    if (!privateCreator.empty())
-    {
-      tag.setPrivateCreator(privateCreator.c_str());
-    }
-
-    return GetTagNameInternal(tag);
-  }
-
-
-  std::string FromDcmtkBridge::GetTagName(const DcmElement& element)
-  {
-    // Copy the tag to ensure const-correctness of DcmElement. Note
-    // that the private creator information is also copied.
-    DcmTag tag(element.getTag());  
-
-    return GetTagNameInternal(tag);
-  }
-
-
-
-  DicomTag FromDcmtkBridge::ParseTag(const char* name)
-  {
-    if (strlen(name) == 9 &&
-        isxdigit(name[0]) &&
-        isxdigit(name[1]) &&
-        isxdigit(name[2]) &&
-        isxdigit(name[3]) &&
-        (name[4] == '-' || name[4] == ',') &&
-        isxdigit(name[5]) &&
-        isxdigit(name[6]) &&
-        isxdigit(name[7]) &&
-        isxdigit(name[8]))        
-    {
-      uint16_t group = GetTagValue(name);
-      uint16_t element = GetTagValue(name + 5);
-      return DicomTag(group, element);
-    }
-
-    if (strlen(name) == 8 &&
-        isxdigit(name[0]) &&
-        isxdigit(name[1]) &&
-        isxdigit(name[2]) &&
-        isxdigit(name[3]) &&
-        isxdigit(name[4]) &&
-        isxdigit(name[5]) &&
-        isxdigit(name[6]) &&
-        isxdigit(name[7]))        
-    {
-      uint16_t group = GetTagValue(name);
-      uint16_t element = GetTagValue(name + 4);
-      return DicomTag(group, element);
-    }
-
-#if 0
-    const DcmDataDictionary& dict = dcmDataDict.rdlock();
-    const DcmDictEntry* entry = dict.findEntry(name);
-
-    if (entry == NULL)
-    {
-      dcmDataDict.unlock();
-      throw OrthancException(ErrorCode_UnknownDicomTag);
-    }
-    else
-    {
-      DcmTagKey key = entry->getKey();
-      DicomTag tag(key.getGroup(), key.getElement());
-      dcmDataDict.unlock();
-      return tag;
-    }
-#else
-    DcmTag tag;
-    if (DcmTag::findTagFromName(name, tag).good())
-    {
-      return DicomTag(tag.getGTag(), tag.getETag());
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_UnknownDicomTag);
-    }
-#endif
-  }
-
-
-  bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag)
-  {
-    DcmTag tmp(tag.GetGroup(), tag.GetElement());
-    return tmp.isUnknownVR();
-  }
-
-
-  void FromDcmtkBridge::ToJson(Json::Value& result,
-                               const DicomMap& values,
-                               bool simplify)
-  {
-    if (result.type() != Json::objectValue)
-    {
-      throw OrthancException(ErrorCode_BadParameterType);
-    }
-
-    result.clear();
-
-    for (DicomMap::Map::const_iterator 
-           it = values.map_.begin(); it != values.map_.end(); ++it)
-    {
-      // TODO Inject PrivateCreator if some is available in the DicomMap?
-      const std::string tagName = GetTagName(it->first, "");
-
-      if (simplify)
-      {
-        if (it->second->IsNull())
-        {
-          result[tagName] = Json::nullValue;
-        }
-        else
-        {
-          // TODO IsBinary
-          result[tagName] = it->second->GetContent();
-        }
-      }
-      else
-      {
-        Json::Value value = Json::objectValue;
-
-        value["Name"] = tagName;
-
-        if (it->second->IsNull())
-        {
-          value["Type"] = "Null";
-          value["Value"] = Json::nullValue;
-        }
-        else
-        {
-          // TODO IsBinary
-          value["Type"] = "String";
-          value["Value"] = it->second->GetContent();
-        }
-
-        result[it->first.Format()] = value;
-      }
-    }
-  }
-
-
-  std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level)
-  {
-    char uid[100];
-
-    switch (level)
-    {
-      case ResourceType_Patient:
-        // The "PatientID" field is of type LO (Long String), 64
-        // Bytes Maximum. An UUID is of length 36, thus it can be used
-        // as a random PatientID.
-        return SystemToolbox::GenerateUuid();
-
-      case ResourceType_Instance:
-        return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
-
-      case ResourceType_Series:
-        return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT);
-
-      case ResourceType_Study:
-        return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT);
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-  bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
-                                           DcmDataset& dataSet)
-  {
-    // Determine the transfer syntax which shall be used to write the
-    // information to the file. We always switch to the Little Endian
-    // syntax, with explicit length.
-
-    // http://support.dcmtk.org/docs/dcxfer_8h-source.html
-
-
-    /**
-     * Note that up to Orthanc 0.7.1 (inclusive), the
-     * "EXS_LittleEndianExplicit" was always used to save the DICOM
-     * dataset into memory. We now keep the original transfer syntax
-     * (if available).
-     **/
-    E_TransferSyntax xfer = dataSet.getOriginalXfer();
-    if (xfer == EXS_Unknown)
-    {
-      // No information about the original transfer syntax: This is
-      // most probably a DICOM dataset that was read from memory.
-      xfer = EXS_LittleEndianExplicit;
-    }
-
-    E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
-
-    // Create the meta-header information
-    DcmFileFormat ff(&dataSet);
-    ff.validateMetaInfo(xfer);
-    ff.removeInvalidGroups();
-
-    // Create a memory buffer with the proper size
-    {
-      const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType);  // (*)
-      buffer.resize(estimatedSize);
-    }
-
-    DcmOutputBufferStream ob(&buffer[0], buffer.size());
-
-    // Fill the memory buffer with the meta-header and the dataset
-    ff.transferInit();
-    OFCondition c = ff.write(ob, xfer, encodingType, NULL,
-                             /*opt_groupLength*/ EGL_recalcGL,
-                             /*opt_paddingType*/ EPD_withoutPadding);
-    ff.transferEnd();
-
-    if (c.good())
-    {
-      // The DICOM file is successfully written, truncate the target
-      // buffer if its size was overestimated by (*)
-      ob.flush();
-
-      size_t effectiveSize = static_cast<size_t>(ob.tell());
-      if (effectiveSize < buffer.size())
-      {
-        buffer.resize(effectiveSize);
-      }
-
-      return true;
-    }
-    else
-    {
-      // Error
-      buffer.clear();
-      return false;
-    }
-  }
-
-
-  ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag)
-  {
-    DcmTag t(tag.GetGroup(), tag.GetElement());
-    return Convert(t.getEVR());
-  }
-
-  ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr)
-  {
-    switch (vr)
-    {
-      case EVR_AE:
-        return ValueRepresentation_ApplicationEntity;
-
-      case EVR_AS:
-        return ValueRepresentation_AgeString;
-
-      case EVR_AT:
-        return ValueRepresentation_AttributeTag;
-
-      case EVR_CS:
-        return ValueRepresentation_CodeString;
-
-      case EVR_DA:
-        return ValueRepresentation_Date;
-
-      case EVR_DS:
-        return ValueRepresentation_DecimalString;
-
-      case EVR_DT:
-        return ValueRepresentation_DateTime;
-
-      case EVR_FL:
-        return ValueRepresentation_FloatingPointSingle;
-
-      case EVR_FD:
-        return ValueRepresentation_FloatingPointDouble;
-
-      case EVR_IS:
-        return ValueRepresentation_IntegerString;
-
-      case EVR_LO:
-        return ValueRepresentation_LongString;
-
-      case EVR_LT:
-        return ValueRepresentation_LongText;
-
-      case EVR_OB:
-        return ValueRepresentation_OtherByte;
-
-        // Not supported as of DCMTK 3.6.0
-        /*case EVR_OD:
-          return ValueRepresentation_OtherDouble;*/
-
-      case EVR_OF:
-        return ValueRepresentation_OtherFloat;
-
-        // Not supported as of DCMTK 3.6.0
-        /*case EVR_OL:
-          return ValueRepresentation_OtherLong;*/
-
-      case EVR_OW:
-        return ValueRepresentation_OtherWord;
-
-      case EVR_PN:
-        return ValueRepresentation_PersonName;
-
-      case EVR_SH:
-        return ValueRepresentation_ShortString;
-
-      case EVR_SL:
-        return ValueRepresentation_SignedLong;
-
-      case EVR_SQ:
-        return ValueRepresentation_Sequence;
-
-      case EVR_SS:
-        return ValueRepresentation_SignedShort;
-
-      case EVR_ST:
-        return ValueRepresentation_ShortText;
-
-      case EVR_TM:
-        return ValueRepresentation_Time;
-
-        // Not supported as of DCMTK 3.6.0
-        /*case EVR_UC:
-          return ValueRepresentation_UnlimitedCharacters;*/
-
-      case EVR_UI:
-        return ValueRepresentation_UniqueIdentifier;
-
-      case EVR_UL:
-        return ValueRepresentation_UnsignedLong;
-
-      case EVR_UN:
-        return ValueRepresentation_Unknown;
-
-        // Not supported as of DCMTK 3.6.0
-        /*case EVR_UR:
-          return ValueRepresentation_UniversalResource;*/
-
-      case EVR_US:
-        return ValueRepresentation_UnsignedShort;
-
-      case EVR_UT:
-        return ValueRepresentation_UnlimitedText;
-
-      default:
-        return ValueRepresentation_NotSupported;
-    }
-  }
-
-
-  static bool IsBinaryTag(const DcmTag& key)
-  {
-    return (key.isUnknownVR() || 
-            key.getEVR() == EVR_OB ||
-            key.getEVR() == EVR_OF ||
-            key.getEVR() == EVR_OW ||
-            key.getEVR() == EVR_UN ||
-            key.getEVR() == EVR_ox);
-  }
-
-
-  DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag)
-  {
-    DcmTag key(tag.GetGroup(), tag.GetElement());
-
-    if (tag.IsPrivate() ||
-        IsBinaryTag(key))
-    {
-      return new DcmOtherByteOtherWord(key);
-    }
-
-    switch (key.getEVR())
-    {
-      // http://support.dcmtk.org/docs/dcvr_8h-source.html
-
-      /**
-       * Binary types, handled above
-       **/
-    
-      case EVR_OB:  // other byte
-      case EVR_OF:  // other float
-      case EVR_OW:  // other word
-      case EVR_UN:  // unknown value representation
-      case EVR_ox:  // OB or OW depending on context
-        throw OrthancException(ErrorCode_InternalError);
-
-
-      /**
-       * String types.
-       * http://support.dcmtk.org/docs/classDcmByteString.html
-       **/
-      
-      case EVR_AS:  // age string
-        return new DcmAgeString(key);
-
-      case EVR_AE:  // application entity title
-        return new DcmApplicationEntity(key);
-
-      case EVR_CS:  // code string
-        return new DcmCodeString(key);        
-
-      case EVR_DA:  // date string
-        return new DcmDate(key);
-        
-      case EVR_DT:  // date time string
-        return new DcmDateTime(key);
-
-      case EVR_DS:  // decimal string
-        return new DcmDecimalString(key);
-
-      case EVR_IS:  // integer string
-        return new DcmIntegerString(key);
-
-      case EVR_TM:  // time string
-        return new DcmTime(key);
-
-      case EVR_UI:  // unique identifier
-        return new DcmUniqueIdentifier(key);
-
-      case EVR_ST:  // short text
-        return new DcmShortText(key);
-
-      case EVR_LO:  // long string
-        return new DcmLongString(key);
-
-      case EVR_LT:  // long text
-        return new DcmLongText(key);
-
-      case EVR_UT:  // unlimited text
-        return new DcmUnlimitedText(key);
-
-      case EVR_SH:  // short string
-        return new DcmShortString(key);
-
-      case EVR_PN:  // person name
-        return new DcmPersonName(key);
-
-        
-      /**
-       * Numerical types
-       **/ 
-      
-      case EVR_SL:  // signed long
-        return new DcmSignedLong(key);
-
-      case EVR_SS:  // signed short
-        return new DcmSignedShort(key);
-
-      case EVR_UL:  // unsigned long
-        return new DcmUnsignedLong(key);
-
-      case EVR_US:  // unsigned short
-        return new DcmUnsignedShort(key);
-
-      case EVR_FL:  // float single-precision
-        return new DcmFloatingPointSingle(key);
-
-      case EVR_FD:  // float double-precision
-        return new DcmFloatingPointDouble(key);
-
-
-      /**
-       * Sequence types, should never occur at this point.
-       **/
-
-      case EVR_SQ:  // sequence of items
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-
-
-      /**
-       * TODO
-       **/
-
-      case EVR_AT:  // attribute tag
-        throw OrthancException(ErrorCode_NotImplemented);
-
-
-      /**
-       * Internal to DCMTK.
-       **/ 
-
-      case EVR_xs:  // SS or US depending on context
-      case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
-      case EVR_na:  // na="not applicable", for data which has no VR
-      case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
-      case EVR_item:  // used internally for items
-      case EVR_metainfo:  // used internally for meta info datasets
-      case EVR_dataset:  // used internally for datasets
-      case EVR_fileFormat:  // used internally for DICOM files
-      case EVR_dicomDir:  // used internally for DICOMDIR objects
-      case EVR_dirRecord:  // used internally for DICOMDIR records
-      case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
-      case EVR_pixelItem:  // used internally for pixel items in a compressed image
-      case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
-      case EVR_PixelData:  // used internally for uncompressed pixeld data
-      case EVR_OverlayData:  // used internally for overlay data
-      case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
-      default:
-        break;
-    }
-
-    throw OrthancException(ErrorCode_InternalError);          
-  }
-
-
-
-  void FromDcmtkBridge::FillElementWithString(DcmElement& element,
-                                              const DicomTag& tag,
-                                              const std::string& utf8Value,
-                                              bool decodeDataUriScheme,
-                                              Encoding dicomEncoding)
-  {
-    std::string binary;
-    const std::string* decoded = &utf8Value;
-
-    if (decodeDataUriScheme &&
-        boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
-    {
-      std::string mime;
-      if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value))
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      decoded = &binary;
-    }
-    else if (dicomEncoding != Encoding_Utf8)
-    {
-      binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding);
-      decoded = &binary;
-    }
-
-    DcmTag key(tag.GetGroup(), tag.GetElement());
-
-    if (tag.IsPrivate() ||
-        IsBinaryTag(key))
-    {
-      if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good())
-      {
-        return;
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-
-    bool ok = false;
-    
-    try
-    {
-      switch (key.getEVR())
-      {
-        // http://support.dcmtk.org/docs/dcvr_8h-source.html
-
-        /**
-         * TODO.
-         **/
-
-        case EVR_OB:  // other byte
-        case EVR_OF:  // other float
-        case EVR_OW:  // other word
-        case EVR_AT:  // attribute tag
-          throw OrthancException(ErrorCode_NotImplemented);
-    
-        case EVR_UN:  // unknown value representation
-          throw OrthancException(ErrorCode_ParameterOutOfRange);
-
-
-        /**
-         * String types.
-         **/
-      
-        case EVR_DS:  // decimal string
-        case EVR_IS:  // integer string
-        case EVR_AS:  // age string
-        case EVR_DA:  // date string
-        case EVR_DT:  // date time string
-        case EVR_TM:  // time string
-        case EVR_AE:  // application entity title
-        case EVR_CS:  // code string
-        case EVR_SH:  // short string
-        case EVR_LO:  // long string
-        case EVR_ST:  // short text
-        case EVR_LT:  // long text
-        case EVR_UT:  // unlimited text
-        case EVR_PN:  // person name
-        case EVR_UI:  // unique identifier
-        {
-          ok = element.putString(decoded->c_str()).good();
-          break;
-        }
-
-        
-        /**
-         * Numerical types
-         **/ 
-      
-        case EVR_SL:  // signed long
-        {
-          ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good();
-          break;
-        }
-
-        case EVR_SS:  // signed short
-        {
-          ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
-          break;
-        }
-
-        case EVR_UL:  // unsigned long
-        {
-          ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good();
-          break;
-        }
-
-        case EVR_US:  // unsigned short
-        {
-          ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();
-          break;
-        }
-
-        case EVR_FL:  // float single-precision
-        {
-          ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good();
-          break;
-        }
-
-        case EVR_FD:  // float double-precision
-        {
-          ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good();
-          break;
-        }
-
-
-        /**
-         * Sequence types, should never occur at this point.
-         **/
-
-        case EVR_SQ:  // sequence of items
-        {
-          ok = false;
-          break;
-        }
-
-
-        /**
-         * Internal to DCMTK.
-         **/ 
-
-        case EVR_ox:  // OB or OW depending on context
-        case EVR_xs:  // SS or US depending on context
-        case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
-        case EVR_na:  // na="not applicable", for data which has no VR
-        case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
-        case EVR_item:  // used internally for items
-        case EVR_metainfo:  // used internally for meta info datasets
-        case EVR_dataset:  // used internally for datasets
-        case EVR_fileFormat:  // used internally for DICOM files
-        case EVR_dicomDir:  // used internally for DICOMDIR objects
-        case EVR_dirRecord:  // used internally for DICOMDIR records
-        case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
-        case EVR_pixelItem:  // used internally for pixel items in a compressed image
-        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
-        case EVR_PixelData:  // used internally for uncompressed pixeld data
-        case EVR_OverlayData:  // used internally for overlay data
-        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
-        default:
-          break;
-      }
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      ok = false;
-    }
-
-    if (!ok)
-    {
-      LOG(ERROR) << "While creating a DICOM instance, tag (" << tag.Format()
-                 << ") has out-of-range value: \"" << *decoded << "\"";
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag,
-                                        const Json::Value& value,
-                                        bool decodeDataUriScheme,
-                                        Encoding dicomEncoding)
-  {
-    std::auto_ptr<DcmElement> element;
-
-    switch (value.type())
-    {
-      case Json::stringValue:
-        element.reset(CreateElementForTag(tag));
-        FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding);
-        break;
-
-      case Json::nullValue:
-        element.reset(CreateElementForTag(tag));
-        FillElementWithString(*element, tag, "", decodeDataUriScheme, dicomEncoding);
-        break;
-
-      case Json::arrayValue:
-      {
-        DcmTag key(tag.GetGroup(), tag.GetElement());
-        if (key.getEVR() != EVR_SQ)
-        {
-          throw OrthancException(ErrorCode_BadParameterType);
-        }
-
-        DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key);
-        element.reset(sequence);
-        
-        for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
-        {
-          std::auto_ptr<DcmItem> item(new DcmItem);
-
-          Json::Value::Members members = value[i].getMemberNames();
-          for (Json::Value::ArrayIndex j = 0; j < members.size(); j++)
-          {
-            item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding));
-          }
-
-          sequence->append(item.release());
-        }
-
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_BadParameterType);
-    }
-
-    return element.release();
-  }
-
-
-  DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset)
-  {
-    DcmElement *element = NULL;
-    if (!dataset.findAndGetElement(DCM_PixelData, element).good())
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
-    DcmPixelSequence* pixelSequence = NULL;
-    if (!pixelData.getEncapsulatedRepresentation
-        (dataset.getOriginalXfer(), NULL, pixelSequence).good())
-    {
-      return NULL;
-    }
-    else
-    {
-      return pixelSequence;
-    }
-  }
-
-
-  Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json,
-                                            Encoding defaultEncoding)
-  {
-    if (json.type() != Json::objectValue)
-    {
-      throw OrthancException(ErrorCode_BadParameterType);
-    }
-
-    Encoding encoding = defaultEncoding;
-
-    const Json::Value::Members tags = json.getMemberNames();
-    
-    // Look for SpecificCharacterSet (0008,0005) in the JSON file
-    for (size_t i = 0; i < tags.size(); i++)
-    {
-      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
-      if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
-      {
-        const Json::Value& value = json[tags[i]];
-        if (value.type() != Json::stringValue ||
-            (value.asString().length() != 0 &&
-             !GetDicomEncoding(encoding, value.asCString())))
-        {
-          LOG(ERROR) << "Unknown encoding while creating DICOM from JSON: " << value;
-          throw OrthancException(ErrorCode_BadRequest);
-        }
-
-        if (value.asString().length() == 0)
-        {
-          return defaultEncoding;
-        }
-      }
-    }
-
-    return encoding;
-  } 
-
-
-  static void SetString(DcmDataset& target,
-                        const DcmTag& tag,
-                        const std::string& value)
-  {
-    if (!target.putAndInsertString(tag, value.c_str()).good())
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-
-
-  DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json,  // Encoded using UTF-8
-                                        bool generateIdentifiers,
-                                        bool decodeDataUriScheme,
-                                        Encoding defaultEncoding)
-  {
-    std::auto_ptr<DcmDataset> result(new DcmDataset);
-    Encoding encoding = ExtractEncoding(json, defaultEncoding);
-
-    SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding));
-
-    const Json::Value::Members tags = json.getMemberNames();
-    
-    bool hasPatientId = false;
-    bool hasStudyInstanceUid = false;
-    bool hasSeriesInstanceUid = false;
-    bool hasSopInstanceUid = false;
-
-    for (size_t i = 0; i < tags.size(); i++)
-    {
-      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
-      const Json::Value& value = json[tags[i]];
-
-      if (tag == DICOM_TAG_PATIENT_ID)
-      {
-        hasPatientId = true;
-      }
-      else if (tag == DICOM_TAG_STUDY_INSTANCE_UID)
-      {
-        hasStudyInstanceUid = true;
-      }
-      else if (tag == DICOM_TAG_SERIES_INSTANCE_UID)
-      {
-        hasSeriesInstanceUid = true;
-      }
-      else if (tag == DICOM_TAG_SOP_INSTANCE_UID)
-      {
-        hasSopInstanceUid = true;
-      }
-
-      if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
-      {
-        std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding));
-        const DcmTagKey& tag = element->getTag();
-
-        result->findAndDeleteElement(tag);
-
-        DcmElement* tmp = element.release();
-        if (!result->insert(tmp, false, false).good())
-        {
-          delete tmp;
-          throw OrthancException(ErrorCode_InternalError);
-        }
-      }
-    }
-
-    if (!hasPatientId &&
-        generateIdentifiers)
-    {
-      SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient));
-    }
-
-    if (!hasStudyInstanceUid &&
-        generateIdentifiers)
-    {
-      SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study));
-    }
-
-    if (!hasSeriesInstanceUid &&
-        generateIdentifiers)
-    {
-      SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series));
-    }
-
-    if (!hasSopInstanceUid &&
-        generateIdentifiers)
-    {
-      SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance));
-    }
-
-    return result.release();
-  }
-
-
-  DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer,
-                                                       size_t size)
-  {
-    DcmInputBufferStream is;
-    if (size > 0)
-    {
-      is.setBuffer(buffer, size);
-    }
-    is.setEos();
-
-    std::auto_ptr<DcmFileFormat> result(new DcmFileFormat);
-
-    result->transferInit();
-    if (!result->read(is).good())
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    result->loadAllDataIntoMemory();
-    result->transferEnd();
-
-    return result.release();
-  }
-
-
-  void FromDcmtkBridge::FromJson(DicomMap& target,
-                                 const Json::Value& source)
-  {
-    if (source.type() != Json::objectValue)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    target.Clear();
-
-    Json::Value::Members members = source.getMemberNames();
-
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      const Json::Value& value = source[members[i]];
-
-      if (value.type() != Json::stringValue)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-      
-      target.SetValue(ParseTag(members[i]), value.asString(), false);
-    }
-  }
-
-
-  void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset,
-                                             Encoding source,
-                                             Encoding target)
-  {
-    // Recursive exploration of a dataset to change the encoding of
-    // each string-like element
-
-    if (source == target)
-    {
-      return;
-    }
-
-    for (unsigned long i = 0; i < dataset.card(); i++)
-    {
-      DcmElement* element = dataset.getElement(i);
-      if (element)
-      {
-        if (element->isLeaf())
-        {
-          char *c = NULL;
-          if (element->isaString() &&
-              element->getString(c).good() && 
-              c != NULL)
-          {
-            std::string a = Toolbox::ConvertToUtf8(c, source);
-            std::string b = Toolbox::ConvertFromUtf8(a, target);
-            element->putString(b.c_str());
-          }
-        }
-        else
-        {
-          // "All subclasses of DcmElement except for DcmSequenceOfItems
-          // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
-          // etc. are not." The following dynamic_cast is thus OK.
-          DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
-
-          for (unsigned long j = 0; j < sequence.card(); j++)
-          {
-            ChangeStringEncoding(*sequence.getItem(j), source, target);
-          }
-        }
-      }
-    }
-  }
-
-
-  bool FromDcmtkBridge::LookupTransferSyntax(std::string& result,
-                                             DcmFileFormat& dicom)
-  {
-    const char* value = NULL;
-
-    if (dicom.getMetaInfo() != NULL &&
-        dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() &&
-        value != NULL)
-    {
-      result.assign(value);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-#if ORTHANC_ENABLE_LUA == 1
-  void FromDcmtkBridge::ExecuteToDicom(DicomMap& target,
-                                       LuaFunctionCall& call)
-  {
-    Json::Value output;
-    call.ExecuteToJson(output, true /* keep strings */);
-
-    target.Clear();
-
-    if (output.type() == Json::arrayValue &&
-        output.size() == 0)
-    {
-      // This case happens for empty tables
-      return;
-    }
-
-    if (output.type() != Json::objectValue)
-    {
-      LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table";
-      throw OrthancException(ErrorCode_LuaBadOutput);
-    }
-
-    Json::Value::Members members = output.getMemberNames();
-
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      if (output[members[i]].type() != Json::stringValue)
-      {
-        LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table mapping names of DICOM tags to strings";
-        throw OrthancException(ErrorCode_LuaBadOutput);
-      }
-
-      DicomTag tag(ParseTag(members[i]));
-      target.SetValue(tag, output[members[i]].asString(), false);
-    }
-  }
-#endif
-
-
-  void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, 
-                                            DcmItem& dataset)
-  {
-    ExtractDicomSummary(target, dataset,
-                        ORTHANC_MAXIMUM_TAG_LENGTH,
-                        GetDefaultDicomEncoding());
-  }
-
-  
-  void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, 
-                                           DcmDataset& dataset)
-  {
-    ExtractDicomAsJson(target, dataset, 
-                       DicomToJsonFormat_Full,
-                       DicomToJsonFlags_Default, 
-                       ORTHANC_MAXIMUM_TAG_LENGTH,
-                       GetDefaultDicomEncoding());
-  }
-
-
-  void FromDcmtkBridge::InitializeCodecs()
-  {
-#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
-    LOG(WARNING) << "Registering JPEG Lossless codecs in DCMTK";
-    DJLSDecoderRegistration::registerCodecs();    
-#endif
-
-#if ORTHANC_ENABLE_DCMTK_JPEG == 1
-    LOG(WARNING) << "Registering JPEG codecs in DCMTK";
-    DJDecoderRegistration::registerCodecs(); 
-#endif
-  }
-
-
-  void FromDcmtkBridge::FinalizeCodecs()
-  {
-#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
-    // Unregister JPEG-LS codecs
-    DJLSDecoderRegistration::cleanup();
-#endif
-
-#if ORTHANC_ENABLE_DCMTK_JPEG == 1
-    // Unregister JPEG codecs
-    DJDecoderRegistration::cleanup();
-#endif
-  }
-}
--- a/OrthancServer/FromDcmtkBridge.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,241 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ServerEnumerations.h"
-
-#include "../Core/DicomFormat/DicomElement.h"
-#include "../Core/DicomFormat/DicomMap.h"
-
-#include <dcmtk/dcmdata/dcdatset.h>
-#include <dcmtk/dcmdata/dcmetinf.h>
-#include <dcmtk/dcmdata/dcpixseq.h>
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <json/json.h>
-
-#if !defined(ORTHANC_ENABLE_LUA)
-#  error The macro ORTHANC_ENABLE_LUA must be defined
-#endif
-
-#if ORTHANC_ENABLE_DCMTK != 1
-#  error The macro ORTHANC_ENABLE_DCMTK must be set to 1
-#endif
-
-#if ORTHANC_BUILD_UNIT_TESTS == 1
-#  include <gtest/gtest_prod.h>
-#endif
-
-#if ORTHANC_ENABLE_LUA == 1
-#  include "../Core/Lua/LuaFunctionCall.h"
-#endif
-
-#if !defined(ORTHANC_ENABLE_DCMTK_JPEG)
-#  error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined
-#endif
-
-#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS)
-#  error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined
-#endif
-
-
-namespace Orthanc
-{
-  class FromDcmtkBridge : public boost::noncopyable
-  {
-#if ORTHANC_BUILD_UNIT_TESTS == 1
-    FRIEND_TEST(FromDcmtkBridge, FromJson);
-#endif
-
-    friend class ParsedDicomFile;
-
-  private:
-    FromDcmtkBridge();  // Pure static class
-
-    static void ExtractDicomSummary(DicomMap& target, 
-                                    DcmItem& dataset,
-                                    unsigned int maxStringLength,
-                                    Encoding defaultEncoding);
-
-    static void DatasetToJson(Json::Value& parent,
-                              DcmItem& item,
-                              DicomToJsonFormat format,
-                              DicomToJsonFlags flags,
-                              unsigned int maxStringLength,
-                              Encoding encoding);
-
-    static void ElementToJson(Json::Value& parent,
-                              DcmElement& element,
-                              DicomToJsonFormat format,
-                              DicomToJsonFlags flags,
-                              unsigned int maxStringLength,
-                              Encoding dicomEncoding);
-
-    static void ExtractDicomAsJson(Json::Value& target, 
-                                   DcmDataset& dataset,
-                                   DicomToJsonFormat format,
-                                   DicomToJsonFlags flags,
-                                   unsigned int maxStringLength,
-                                   Encoding defaultEncoding);
-
-    static void ChangeStringEncoding(DcmItem& dataset,
-                                     Encoding source,
-                                     Encoding target);
-
-  public:
-    static void InitializeDictionary(bool loadPrivateDictionary);
-
-    static void RegisterDictionaryTag(const DicomTag& tag,
-                                      ValueRepresentation vr,
-                                      const std::string& name,
-                                      unsigned int minMultiplicity,
-                                      unsigned int maxMultiplicity,
-                                      const std::string& privateCreator);
-
-    static Encoding DetectEncoding(DcmItem& dataset,
-                                   Encoding defaultEncoding);
-
-    static DicomTag Convert(const DcmTag& tag);
-
-    static DicomTag GetTag(const DcmElement& element);
-
-    static bool IsUnknownTag(const DicomTag& tag);
-
-    static DicomValue* ConvertLeafElement(DcmElement& element,
-                                          DicomToJsonFlags flags,
-                                          unsigned int maxStringLength,
-                                          Encoding encoding);
-
-    static void ExtractHeaderAsJson(Json::Value& target, 
-                                    DcmMetaInfo& header,
-                                    DicomToJsonFormat format,
-                                    DicomToJsonFlags flags,
-                                    unsigned int maxStringLength);
-
-    static std::string GetTagName(const DicomTag& tag,
-                                  const std::string& privateCreator);
-
-    static std::string GetTagName(const DcmElement& element);
-
-    static std::string GetTagName(const DicomElement& element)
-    {
-      return GetTagName(element.GetTag(), "");
-    }
-
-    static DicomTag ParseTag(const char* name);
-
-    static DicomTag ParseTag(const std::string& name)
-    {
-      return ParseTag(name.c_str());
-    }
-
-    static bool HasTag(const DicomMap& fields,
-                       const std::string& tagName)
-    {
-      return fields.HasTag(ParseTag(tagName));
-    }
-
-    static const DicomValue& GetValue(const DicomMap& fields,
-                                      const std::string& tagName)
-    {
-      return fields.GetValue(ParseTag(tagName));
-    }
-
-    static void SetValue(DicomMap& target,
-                         const std::string& tagName,
-                         DicomValue* value)
-    {
-      target.SetValue(ParseTag(tagName), value);
-    }
-
-    static void ToJson(Json::Value& result,
-                       const DicomMap& values,
-                       bool simplify);
-
-    static std::string GenerateUniqueIdentifier(ResourceType level);
-
-    static bool SaveToMemoryBuffer(std::string& buffer,
-                                   DcmDataset& dataSet);
-
-    static ValueRepresentation Convert(DcmEVR vr);
-
-    static ValueRepresentation LookupValueRepresentation(const DicomTag& tag);
-
-    static DcmElement* CreateElementForTag(const DicomTag& tag);
-    
-    static void FillElementWithString(DcmElement& element,
-                                      const DicomTag& tag,
-                                      const std::string& utf8alue,  // Encoded using UTF-8
-                                      bool decodeDataUriScheme,
-                                      Encoding dicomEncoding);
-
-    static DcmElement* FromJson(const DicomTag& tag,
-                                const Json::Value& element,  // Encoded using UTF-8
-                                bool decodeDataUriScheme,
-                                Encoding dicomEncoding);
-
-    static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset);
-
-    static Encoding ExtractEncoding(const Json::Value& json,
-                                    Encoding defaultEncoding);
-
-    static DcmDataset* FromJson(const Json::Value& json,  // Encoded using UTF-8
-                                bool generateIdentifiers,
-                                bool decodeDataUriScheme,
-                                Encoding defaultEncoding);
-
-    static DcmFileFormat* LoadFromMemoryBuffer(const void* buffer,
-                                               size_t size);
-
-    static void FromJson(DicomMap& values,
-                         const Json::Value& result);
-
-    static bool LookupTransferSyntax(std::string& result,
-                                     DcmFileFormat& dicom);
-
-#if ORTHANC_ENABLE_LUA == 1
-    static void ExecuteToDicom(DicomMap& target,
-                               LuaFunctionCall& call);
-#endif
-
-    static void ExtractDicomSummary(DicomMap& target, 
-                                    DcmItem& dataset);
-
-    static void ExtractDicomAsJson(Json::Value& target, 
-                                   DcmDataset& dataset);
-
-    static void InitializeCodecs();
-
-    static void FinalizeCodecs();
-  };
-}
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,934 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-
-
-/*=========================================================================
-
-  This file is based on portions of the following project:
-
-  Program: DCMTK 3.6.0
-  Module:  http://dicom.offis.de/dcmtk.php.en
-
-Copyright (C) 1994-2011, OFFIS e.V.
-All rights reserved.
-
-This software and supporting documentation were developed by
-
-  OFFIS e.V.
-  R&D Division Health
-  Escherweg 2
-  26121 Oldenburg, Germany
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-- Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-- Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-- Neither the name of OFFIS nor the names of its contributors may be
-  used to endorse or promote products derived from this software
-  without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-=========================================================================*/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "CommandDispatcher.h"
-
-#include "FindScp.h"
-#include "StoreScp.h"
-#include "MoveScp.h"
-#include "../../Core/Toolbox.h"
-#include "../../Core/Logging.h"
-
-#include <dcmtk/dcmnet/dcasccfg.h>      /* for class DcmAssociationConfiguration */
-#include <boost/lexical_cast.hpp>
-
-static OFBool    opt_rejectWithoutImplementationUID = OFFalse;
-
-
-
-static DUL_PRESENTATIONCONTEXT *
-findPresentationContextID(LST_HEAD * head,
-                          T_ASC_PresentationContextID presentationContextID)
-{
-  DUL_PRESENTATIONCONTEXT *pc;
-  LST_HEAD **l;
-  OFBool found = OFFalse;
-
-  if (head == NULL)
-    return NULL;
-
-  l = &head;
-  if (*l == NULL)
-    return NULL;
-
-  pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l));
-  (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc));
-
-  while (pc && !found) {
-    if (pc->presentationContextID == presentationContextID) {
-      found = OFTrue;
-    } else {
-      pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l));
-    }
-  }
-  return pc;
-}
-
-
-/** accept all presenstation contexts for unknown SOP classes,
- *  i.e. UIDs appearing in the list of abstract syntaxes
- *  where no corresponding name is defined in the UID dictionary.
- *  @param params pointer to association parameters structure
- *  @param transferSyntax transfer syntax to accept
- *  @param acceptedRole SCU/SCP role to accept
- */
-static OFCondition acceptUnknownContextsWithTransferSyntax(
-  T_ASC_Parameters * params,
-  const char* transferSyntax,
-  T_ASC_SC_ROLE acceptedRole)
-{
-  OFCondition cond = EC_Normal;
-  int n, i, k;
-  DUL_PRESENTATIONCONTEXT *dpc;
-  T_ASC_PresentationContext pc;
-  OFBool accepted = OFFalse;
-  OFBool abstractOK = OFFalse;
-
-  n = ASC_countPresentationContexts(params);
-  for (i = 0; i < n; i++)
-  {
-    cond = ASC_getPresentationContext(params, i, &pc);
-    if (cond.bad()) return cond;
-    abstractOK = OFFalse;
-    accepted = OFFalse;
-
-    if (dcmFindNameOfUID(pc.abstractSyntax) == NULL)
-    {
-      abstractOK = OFTrue;
-
-      /* check the transfer syntax */
-      for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++)
-      {
-        if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0)
-        {
-          accepted = OFTrue;
-        }
-      }
-    }
-
-    if (accepted)
-    {
-      cond = ASC_acceptPresentationContext(
-        params, pc.presentationContextID,
-        transferSyntax, acceptedRole);
-      if (cond.bad()) return cond;
-    } else {
-      T_ASC_P_ResultReason reason;
-
-      /* do not refuse if already accepted */
-      dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext,
-                                      pc.presentationContextID);
-      if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE)))
-      {
-
-        if (abstractOK) {
-          reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;
-        } else {
-          reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED;
-        }
-        /*
-         * If previously this presentation context was refused
-         * because of bad transfer syntax let it stay that way.
-         */
-        if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED))
-          reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;
-
-        cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason);
-        if (cond.bad()) return cond;
-      }
-    }
-  }
-  return EC_Normal;
-}
-
-
-/** accept all presenstation contexts for unknown SOP classes,
- *  i.e. UIDs appearing in the list of abstract syntaxes
- *  where no corresponding name is defined in the UID dictionary.
- *  This method is passed a list of "preferred" transfer syntaxes.
- *  @param params pointer to association parameters structure
- *  @param transferSyntax transfer syntax to accept
- *  @param acceptedRole SCU/SCP role to accept
- */
-static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes(
-  T_ASC_Parameters * params,
-  const char* transferSyntaxes[], int transferSyntaxCount,
-  T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT)
-{
-  OFCondition cond = EC_Normal;
-  /*
-  ** Accept in the order "least wanted" to "most wanted" transfer
-  ** syntax.  Accepting a transfer syntax will override previously
-  ** accepted transfer syntaxes.
-  */
-  for (int i = transferSyntaxCount - 1; i >= 0; i--)
-  {
-    cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole);
-    if (cond.bad()) return cond;
-  }
-  return cond;
-}
-
-
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    /**
-     * EXTRACT OF FILE "dcmdata/libsrc/dcuid.cc" FROM DCMTK 3.6.0
-     * (dcmAllStorageSOPClassUIDs).
-     *
-     * an array of const strings containing all known Storage SOP
-     * Classes that fit into the conventional
-     * PATIENT-STUDY-SERIES-INSTANCE information model,
-     * i.e. everything a Storage SCP might want to store in a PACS.
-     * Special cases such as hanging protocol storage or the Storage
-     * SOP Class are not included in this list.
-     *
-     * THIS LIST CONTAINS ALL STORAGE SOP CLASSES INCLUDING RETIRED
-     * ONES AND IS LARGER THAN 64 ENTRIES.
-     */
-
-    const char* orthancStorageSOPClassUIDs[] =
-    {
-      UID_AmbulatoryECGWaveformStorage,
-      UID_ArterialPulseWaveformStorage,
-      UID_AutorefractionMeasurementsStorage,
-      UID_BasicStructuredDisplayStorage,
-      UID_BasicTextSRStorage,
-      UID_BasicVoiceAudioWaveformStorage,
-      UID_BlendingSoftcopyPresentationStateStorage,
-      UID_BreastTomosynthesisImageStorage,
-      UID_CardiacElectrophysiologyWaveformStorage,
-      UID_ChestCADSRStorage,
-      UID_ColonCADSRStorage,
-      UID_ColorSoftcopyPresentationStateStorage,
-      UID_ComprehensiveSRStorage,
-      UID_ComputedRadiographyImageStorage,
-      UID_CTImageStorage,
-      UID_DeformableSpatialRegistrationStorage,
-      UID_DigitalIntraOralXRayImageStorageForPresentation,
-      UID_DigitalIntraOralXRayImageStorageForProcessing,
-      UID_DigitalMammographyXRayImageStorageForPresentation,
-      UID_DigitalMammographyXRayImageStorageForProcessing,
-      UID_DigitalXRayImageStorageForPresentation,
-      UID_DigitalXRayImageStorageForProcessing,
-      UID_EncapsulatedCDAStorage,
-      UID_EncapsulatedPDFStorage,
-      UID_EnhancedCTImageStorage,
-      UID_EnhancedMRColorImageStorage,
-      UID_EnhancedMRImageStorage,
-      UID_EnhancedPETImageStorage,
-      UID_EnhancedSRStorage,
-      UID_EnhancedUSVolumeStorage,
-      UID_EnhancedXAImageStorage,
-      UID_EnhancedXRFImageStorage,
-      UID_GeneralAudioWaveformStorage,
-      UID_GeneralECGWaveformStorage,
-      UID_GenericImplantTemplateStorage,
-      UID_GrayscaleSoftcopyPresentationStateStorage,
-      UID_HemodynamicWaveformStorage,
-      UID_ImplantAssemblyTemplateStorage,
-      UID_ImplantationPlanSRDocumentStorage,
-      UID_ImplantTemplateGroupStorage,
-      UID_IntraocularLensCalculationsStorage,
-      UID_KeratometryMeasurementsStorage,
-      UID_KeyObjectSelectionDocumentStorage,
-      UID_LensometryMeasurementsStorage,
-      UID_MacularGridThicknessAndVolumeReportStorage,
-      UID_MammographyCADSRStorage,
-      UID_MRImageStorage,
-      UID_MRSpectroscopyStorage,
-      UID_MultiframeGrayscaleByteSecondaryCaptureImageStorage,
-      UID_MultiframeGrayscaleWordSecondaryCaptureImageStorage,
-      UID_MultiframeSingleBitSecondaryCaptureImageStorage,
-      UID_MultiframeTrueColorSecondaryCaptureImageStorage,
-      UID_NuclearMedicineImageStorage,
-      UID_OphthalmicAxialMeasurementsStorage,
-      UID_OphthalmicPhotography16BitImageStorage,
-      UID_OphthalmicPhotography8BitImageStorage,
-      UID_OphthalmicTomographyImageStorage,
-      UID_OphthalmicVisualFieldStaticPerimetryMeasurementsStorage,
-      UID_PositronEmissionTomographyImageStorage,
-      UID_ProcedureLogStorage,
-      UID_PseudoColorSoftcopyPresentationStateStorage,
-      UID_RawDataStorage,
-      UID_RealWorldValueMappingStorage,
-      UID_RespiratoryWaveformStorage,
-      UID_RTBeamsTreatmentRecordStorage,
-      UID_RTBrachyTreatmentRecordStorage,
-      UID_RTDoseStorage,
-      UID_RTImageStorage,
-      UID_RTIonBeamsTreatmentRecordStorage,
-      UID_RTIonPlanStorage,
-      UID_RTPlanStorage,
-      UID_RTStructureSetStorage,
-      UID_RTTreatmentSummaryRecordStorage,
-      UID_SecondaryCaptureImageStorage,
-      UID_SegmentationStorage,
-      UID_SpatialFiducialsStorage,
-      UID_SpatialRegistrationStorage,
-      UID_SpectaclePrescriptionReportStorage,
-      UID_StereometricRelationshipStorage,
-      UID_SubjectiveRefractionMeasurementsStorage,
-      UID_SurfaceSegmentationStorage,
-      UID_TwelveLeadECGWaveformStorage,
-      UID_UltrasoundImageStorage,
-      UID_UltrasoundMultiframeImageStorage,
-      UID_VideoEndoscopicImageStorage,
-      UID_VideoMicroscopicImageStorage,
-      UID_VideoPhotographicImageStorage,
-      UID_VisualAcuityMeasurementsStorage,
-      UID_VLEndoscopicImageStorage,
-      UID_VLMicroscopicImageStorage,
-      UID_VLPhotographicImageStorage,
-      UID_VLSlideCoordinatesMicroscopicImageStorage,
-      UID_VLWholeSlideMicroscopyImageStorage,
-      UID_XAXRFGrayscaleSoftcopyPresentationStateStorage,
-      UID_XRay3DAngiographicImageStorage,
-      UID_XRay3DCraniofacialImageStorage,
-      UID_XRayAngiographicImageStorage,
-      UID_XRayRadiationDoseSRStorage,
-      UID_XRayRadiofluoroscopicImageStorage,
-      // retired
-      UID_RETIRED_HardcopyColorImageStorage,
-      UID_RETIRED_HardcopyGrayscaleImageStorage,
-      UID_RETIRED_NuclearMedicineImageStorage,
-      UID_RETIRED_StandaloneCurveStorage,
-      UID_RETIRED_StandaloneModalityLUTStorage,
-      UID_RETIRED_StandaloneOverlayStorage,
-      UID_RETIRED_StandalonePETCurveStorage,
-      UID_RETIRED_StandaloneVOILUTStorage,
-      UID_RETIRED_StoredPrintStorage,
-      UID_RETIRED_UltrasoundImageStorage,
-      UID_RETIRED_UltrasoundMultiframeImageStorage,
-      UID_RETIRED_VLImageStorage,
-      UID_RETIRED_VLMultiFrameImageStorage,
-      UID_RETIRED_XRayAngiographicBiPlaneImageStorage,
-      // draft
-      UID_DRAFT_SRAudioStorage,
-      UID_DRAFT_SRComprehensiveStorage,
-      UID_DRAFT_SRDetailStorage,
-      UID_DRAFT_SRTextStorage,
-      UID_DRAFT_WaveformStorage,
-      UID_DRAFT_RTBeamsDeliveryInstructionStorage,
-      NULL
-    };
-
-    const int orthancStorageSOPClassUIDsCount = (sizeof(orthancStorageSOPClassUIDs) / sizeof(const char*)) - 1;
-
-
-
-    OFCondition AssociationCleanup(T_ASC_Association *assoc)
-    {
-      OFCondition cond = ASC_dropSCPAssociation(assoc);
-      if (cond.bad())
-      {
-        LOG(FATAL) << cond.text();
-        return cond;
-      }
-
-      cond = ASC_destroyAssociation(&assoc);
-      if (cond.bad())
-      {
-        LOG(FATAL) << cond.text();
-        return cond;
-      }
-
-      return cond;
-    }
-
-
-
-    CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net)
-    {
-      DcmAssociationConfiguration asccfg;
-      char buf[BUFSIZ];
-      T_ASC_Association *assoc;
-      OFCondition cond;
-      OFString sprofile;
-      OFString temp_str;
-
-      std::vector<const char*> knownAbstractSyntaxes;
-
-      // For C-STORE
-      if (server.HasStoreRequestHandlerFactory())
-      {
-        knownAbstractSyntaxes.push_back(UID_VerificationSOPClass);
-      }
-
-      // For C-FIND
-      if (server.HasFindRequestHandlerFactory())
-      {
-        knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
-        knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
-      }
-
-      if (server.HasWorklistRequestHandlerFactory())
-      {
-        knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel);
-      }
-
-      // For C-MOVE
-      if (server.HasMoveRequestHandlerFactory())
-      {
-        knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
-        knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel);
-      }
-
-      cond = ASC_receiveAssociation(net, &assoc, 
-                                    /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, 
-                                    NULL, NULL,
-                                    /*opt_secureConnection*/ OFFalse,
-                                    DUL_NOBLOCK, 1);
-
-      if (cond == DUL_NOASSOCIATIONREQUEST)
-      {
-        // Timeout
-        AssociationCleanup(assoc);
-        return NULL;
-      }
-
-      // if some kind of error occured, take care of it
-      if (cond.bad())
-      {
-        LOG(ERROR) << "Receiving Association failed: " << cond.text();
-        // no matter what kind of error occurred, we need to do a cleanup
-        AssociationCleanup(assoc);
-        return NULL;
-      }
-
-      // Retrieve the AET and the IP address of the remote modality
-      std::string remoteAet;
-      std::string remoteIp;
-      std::string calledAet;
-  
-      {
-        DIC_AE remoteAet_C;
-        DIC_AE calledAet_C;
-        DIC_AE remoteIp_C;
-        DIC_AE calledIP_C;
-        if (ASC_getAPTitles(assoc->params, remoteAet_C, calledAet_C, NULL).bad() ||
-            ASC_getPresentationAddresses(assoc->params, remoteIp_C, calledIP_C).bad())
-        {
-          T_ASC_RejectParameters rej =
-            {
-              ASC_RESULT_REJECTEDPERMANENT,
-              ASC_SOURCE_SERVICEUSER,
-              ASC_REASON_SU_NOREASON
-            };
-          ASC_rejectAssociation(assoc, &rej);
-          AssociationCleanup(assoc);
-          return NULL;
-        }
-
-        remoteIp = std::string(/*OFSTRING_GUARD*/(remoteIp_C));
-        remoteAet = std::string(/*OFSTRING_GUARD*/(remoteAet_C));
-        calledAet = (/*OFSTRING_GUARD*/(calledAet_C));
-      }
-
-      LOG(INFO) << "Association Received from AET " << remoteAet 
-                << " on IP " << remoteIp;
-
-
-      std::vector<const char*> transferSyntaxes;
-
-      // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1
-      transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
-      transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
-      transferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
-
-      // New transfer syntaxes supported since Orthanc 0.7.2
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated))
-      {
-        transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); 
-      }
-
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg))
-      {
-        transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax);
-      }
-
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000))
-      {
-        transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
-        transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
-      }
-
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless))
-      {
-        transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
-        transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax);
-        transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax);
-      }
-
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip))
-      {
-        transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax);
-        transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax);
-      }
-
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2))
-      {
-        transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax);
-        transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax);
-      }
-
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle))
-      {
-        transferSyntaxes.push_back(UID_RLELosslessTransferSyntax);
-      }
-
-      /* accept the Verification SOP Class if presented */
-      cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), &transferSyntaxes[0], transferSyntaxes.size());
-      if (cond.bad())
-      {
-        LOG(INFO) << cond.text();
-        AssociationCleanup(assoc);
-        return NULL;
-      }
-
-      /* the array of Storage SOP Class UIDs comes from dcuid.h */
-      cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, orthancStorageSOPClassUIDs, orthancStorageSOPClassUIDsCount, &transferSyntaxes[0], transferSyntaxes.size());
-      if (cond.bad())
-      {
-        LOG(INFO) << cond.text();
-        AssociationCleanup(assoc);
-        return NULL;
-      }
-
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet))
-      {
-        /*
-         * Promiscous mode is enabled: Accept everything not known not
-         * to be a storage SOP class.
-         **/
-        cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
-          assoc->params, &transferSyntaxes[0], transferSyntaxes.size());
-        if (cond.bad())
-        {
-          LOG(INFO) << cond.text();
-          AssociationCleanup(assoc);
-          return NULL;
-        }
-      }
-
-      /* set our app title */
-      ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str());
-
-      /* acknowledge or reject this association */
-      cond = ASC_getApplicationContextName(assoc->params, buf);
-      if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0)
-      {
-        /* reject: the application context name is not supported */
-        T_ASC_RejectParameters rej =
-          {
-            ASC_RESULT_REJECTEDPERMANENT,
-            ASC_SOURCE_SERVICEUSER,
-            ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED
-          };
-
-        LOG(INFO) << "Association Rejected: Bad Application Context Name: " << buf;
-        cond = ASC_rejectAssociation(assoc, &rej);
-        if (cond.bad())
-        {
-          LOG(INFO) << cond.text();
-        }
-        AssociationCleanup(assoc);
-        return NULL;
-      }
-
-      /* check the AETs */
-      if (!server.IsMyAETitle(calledAet))
-      {
-        LOG(WARNING) << "Rejected association, because of a bad called AET in the request (" << calledAet << ")";
-        T_ASC_RejectParameters rej =
-          {
-            ASC_RESULT_REJECTEDPERMANENT,
-            ASC_SOURCE_SERVICEUSER,
-            ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED
-          };
-        ASC_rejectAssociation(assoc, &rej);
-        AssociationCleanup(assoc);
-        return NULL;
-      }
-
-      if (server.HasApplicationEntityFilter() &&
-          !server.GetApplicationEntityFilter().IsAllowedConnection(remoteIp, remoteAet, calledAet))
-      {
-        LOG(WARNING) << "Rejected association for remote AET " << remoteAet << " on IP " << remoteIp;
-        T_ASC_RejectParameters rej =
-          {
-            ASC_RESULT_REJECTEDPERMANENT,
-            ASC_SOURCE_SERVICEUSER,
-            ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED
-          };
-        ASC_rejectAssociation(assoc, &rej);
-        AssociationCleanup(assoc);
-        return NULL;
-      }
-
-      if (opt_rejectWithoutImplementationUID && 
-          strlen(assoc->params->theirImplementationClassUID) == 0)
-      {
-        /* reject: the no implementation Class UID provided */
-        T_ASC_RejectParameters rej =
-          {
-            ASC_RESULT_REJECTEDPERMANENT,
-            ASC_SOURCE_SERVICEUSER,
-            ASC_REASON_SU_NOREASON
-          };
-
-        LOG(INFO) << "Association Rejected: No Implementation Class UID provided";
-        cond = ASC_rejectAssociation(assoc, &rej);
-        if (cond.bad())
-        {
-          LOG(INFO) << cond.text();
-        }
-        AssociationCleanup(assoc);
-        return NULL;
-      }
-
-      {
-        cond = ASC_acknowledgeAssociation(assoc);
-        if (cond.bad())
-        {
-          LOG(ERROR) << cond.text();
-          AssociationCleanup(assoc);
-          return NULL;
-        }
-        LOG(INFO) << "Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")";
-        if (ASC_countAcceptedPresentationContexts(assoc->params) == 0)
-          LOG(INFO) << "    (but no valid presentation contexts)";
-      }
-
-      IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL;
-      return new CommandDispatcher(server, assoc, remoteIp, remoteAet, calledAet, filter);
-    }
-
-
-    CommandDispatcher::CommandDispatcher(const DicomServer& server,
-                                         T_ASC_Association* assoc,
-                                         const std::string& remoteIp,
-                                         const std::string& remoteAet,
-                                         const std::string& calledAet,
-                                         IApplicationEntityFilter* filter) :
-      server_(server),
-      assoc_(assoc),
-      remoteIp_(remoteIp),
-      remoteAet_(remoteAet),
-      calledAet_(calledAet),
-      filter_(filter)
-    {
-      associationTimeout_ = server.GetAssociationTimeout();
-      elapsedTimeSinceLastCommand_ = 0;
-    }
-
-
-    CommandDispatcher::~CommandDispatcher()
-    {
-      try
-      {
-        AssociationCleanup(assoc_);
-      }
-      catch (...)
-      {
-        LOG(ERROR) << "Some association was not cleanly aborted";
-      }
-    }
-
-
-    bool CommandDispatcher::Step()
-    /*
-     * This function receives DIMSE commmands over the network connection
-     * and handles these commands correspondingly. Note that in case of
-     * storscp only C-ECHO-RQ and C-STORE-RQ commands can be processed.
-     */
-    {
-      bool finished = false;
-
-      // receive a DIMSE command over the network, with a timeout of 1 second
-      DcmDataset *statusDetail = NULL;
-      T_ASC_PresentationContextID presID = 0;
-      T_DIMSE_Message msg;
-
-      OFCondition cond = DIMSE_receiveCommand(assoc_, DIMSE_NONBLOCKING, 1, &presID, &msg, &statusDetail);
-      elapsedTimeSinceLastCommand_++;
-    
-      // if the command which was received has extra status
-      // detail information, dump this information
-      if (statusDetail != NULL)
-      {
-        //LOG4CPP_WARN(Internals::GetLogger(), "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
-        delete statusDetail;
-      }
-
-      if (cond == DIMSE_OUTOFRESOURCES)
-      {
-        finished = true;
-      }
-      else if (cond == DIMSE_NODATAAVAILABLE)
-      {
-        // Timeout due to DIMSE_NONBLOCKING
-        if (associationTimeout_ != 0 && 
-            elapsedTimeSinceLastCommand_ >= associationTimeout_)
-        {
-          // This timeout is actually a association timeout
-          finished = true;
-        }
-      }
-      else if (cond == EC_Normal)
-      {
-        // Reset the association timeout counter
-        elapsedTimeSinceLastCommand_ = 0;
-
-        // Convert the type of request to Orthanc's internal type
-        bool supported = false;
-        DicomRequestType request;
-        switch (msg.CommandField)
-        {
-          case DIMSE_C_ECHO_RQ:
-            request = DicomRequestType_Echo;
-            supported = true;
-            break;
-
-          case DIMSE_C_STORE_RQ:
-            request = DicomRequestType_Store;
-            supported = true;
-            break;
-
-          case DIMSE_C_MOVE_RQ:
-            request = DicomRequestType_Move;
-            supported = true;
-            break;
-
-          case DIMSE_C_FIND_RQ:
-            request = DicomRequestType_Find;
-            supported = true;
-            break;
-
-          default:
-            // we cannot handle this kind of message
-            cond = DIMSE_BADCOMMANDTYPE;
-            LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField;
-            break;
-        }
-
-
-        // Check whether this request is allowed by the security filter
-        if (supported && 
-            filter_ != NULL &&
-            !filter_->IsAllowedRequest(remoteIp_, remoteAet_, calledAet_, request))
-        {
-          LOG(WARNING) << "Rejected " << EnumerationToString(request)
-                       << " request from remote DICOM modality with AET \""
-                       << remoteAet_ << "\" and hostname \"" << remoteIp_ << "\"";
-          cond = DIMSE_ILLEGALASSOCIATION;
-          supported = false;
-          finished = true;
-        }
-
-        // in case we received a supported message, process this command
-        if (supported)
-        {
-          // If anything goes wrong, there will be a "BADCOMMANDTYPE" answer
-          cond = DIMSE_BADCOMMANDTYPE;
-
-          switch (request)
-          {
-            case DicomRequestType_Echo:
-              cond = EchoScp(assoc_, &msg, presID);
-              break;
-
-            case DicomRequestType_Store:
-              if (server_.HasStoreRequestHandlerFactory()) // Should always be true
-              {
-                std::auto_ptr<IStoreRequestHandler> handler
-                  (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler());
-
-                if (handler.get() != NULL)
-                {
-                  cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_);
-                }
-              }
-              break;
-
-            case DicomRequestType_Move:
-              if (server_.HasMoveRequestHandlerFactory()) // Should always be true
-              {
-                std::auto_ptr<IMoveRequestHandler> handler
-                  (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler());
-
-                if (handler.get() != NULL)
-                {
-                  cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_);
-                }
-              }
-              break;
-
-            case DicomRequestType_Find:
-              if (server_.HasFindRequestHandlerFactory() || // Should always be true
-                  server_.HasWorklistRequestHandlerFactory())
-              {
-                std::auto_ptr<IFindRequestHandler> findHandler;
-                if (server_.HasFindRequestHandlerFactory())
-                {
-                  findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler());
-                }
-
-                std::auto_ptr<IWorklistRequestHandler> worklistHandler;
-                if (server_.HasWorklistRequestHandlerFactory())
-                {
-                  worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler());
-                }
-
-                cond = Internals::findScp(assoc_, &msg, presID, server_.GetRemoteModalities(),
-                                          findHandler.get(), worklistHandler.get(),
-                                          remoteIp_, remoteAet_, calledAet_);
-              }
-              break;
-
-            default:
-              // Should never happen
-              break;
-          }
-        }
-      }
-      else
-      {
-        // Bad status, which indicates the closing of the connection by
-        // the peer or a network error
-        finished = true;
-
-        LOG(INFO) << cond.text();
-      }
-    
-      if (finished)
-      {
-        if (cond == DUL_PEERREQUESTEDRELEASE)
-        {
-          LOG(INFO) << "Association Release";
-          ASC_acknowledgeRelease(assoc_);
-        }
-        else if (cond == DUL_PEERABORTEDASSOCIATION)
-        {
-          LOG(INFO) << "Association Aborted";
-        }
-        else
-        {
-          OFString temp_str;
-          LOG(INFO) << "DIMSE failure (aborting association): " << cond.text();
-          /* some kind of error so abort the association */
-          ASC_abortAssociation(assoc_);
-        }
-      }
-
-      return !finished;
-    }
-
-
-    OFCondition EchoScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID)
-    {
-      OFString temp_str;
-      LOG(INFO) << "Received Echo Request";
-      //LOG(DEBUG) << DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID));
-
-      /* the echo succeeded !! */
-      OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL);
-      if (cond.bad())
-      {
-        LOG(ERROR) << "Echo SCP Failed: " << cond.text();
-      }
-      return cond;
-    }
-  }
-}
--- a/OrthancServer/Internals/CommandDispatcher.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../DicomProtocol/DicomServer.h"
-#include "../../Core/MultiThreading/IRunnableBySteps.h"
-
-#include <dcmtk/dcmnet/dimse.h>
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    OFCondition AssociationCleanup(T_ASC_Association *assoc);
-
-    class CommandDispatcher : public IRunnableBySteps
-    {
-    private:
-      uint32_t associationTimeout_;
-      uint32_t elapsedTimeSinceLastCommand_;
-      const DicomServer& server_;
-      T_ASC_Association* assoc_;
-      std::string remoteIp_;
-      std::string remoteAet_;
-      std::string calledAet_;
-      IApplicationEntityFilter* filter_;
-
-    public:
-      CommandDispatcher(const DicomServer& server,
-                        T_ASC_Association* assoc,
-                        const std::string& remoteIp,
-                        const std::string& remoteAet,
-                        const std::string& calledAet,
-                        IApplicationEntityFilter* filter);
-
-      virtual ~CommandDispatcher();
-
-      virtual bool Step();
-    };
-
-    OFCondition EchoScp(T_ASC_Association * assoc, 
-                        T_DIMSE_Message * msg, 
-                        T_ASC_PresentationContextID presID);
-
-    CommandDispatcher* AcceptAssociation(const DicomServer& server, 
-                                         T_ASC_Network *net);
-  }
-}
--- a/OrthancServer/Internals/DicomFrameIndex.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,439 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * 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 "DicomFrameIndex.h"
-
-#include "../../Core/OrthancException.h"
-#include "../../Core/DicomFormat/DicomImageInformation.h"
-#include "../FromDcmtkBridge.h"
-#include "../../Core/Endianness.h"
-#include "DicomImageDecoder.h"
-
-#include <boost/lexical_cast.hpp>
-
-#include <dcmtk/dcmdata/dcdeftag.h>
-#include <dcmtk/dcmdata/dcpxitem.h>
-#include <dcmtk/dcmdata/dcpixseq.h>
-
-namespace Orthanc
-{
-  class DicomFrameIndex::FragmentIndex : public DicomFrameIndex::IIndex
-  {
-  private:
-    DcmPixelSequence*           pixelSequence_;
-    std::vector<DcmPixelItem*>  startFragment_;
-    std::vector<unsigned int>   countFragments_;
-    std::vector<unsigned int>   frameSize_;
-
-    void GetOffsetTable(std::vector<uint32_t>& table)
-    {
-      DcmPixelItem* item = NULL;
-      if (!pixelSequence_->getItem(item, 0).good() ||
-          item == NULL)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      uint32_t length = item->getLength();
-      if (length == 0)
-      {
-        table.clear();
-        return;
-      }
-
-      if (length % 4 != 0)
-      {
-        // Error: Each fragment is index with 4 bytes (uint32_t)
-        throw OrthancException(ErrorCode_BadFileFormat);        
-      }
-
-      uint8_t* content = NULL;
-      if (!item->getUint8Array(content).good() ||
-          content == NULL)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-
-      table.resize(length / 4);
-
-      // The offset table is always in little endian in the DICOM
-      // file. Swap it to host endianness if needed.
-      const uint32_t* offset = reinterpret_cast<const uint32_t*>(content);
-      for (size_t i = 0; i < table.size(); i++, offset++)
-      {
-        table[i] = le32toh(*offset);
-      }
-    }
-
-
-  public:
-    FragmentIndex(DcmPixelSequence* pixelSequence,
-                  unsigned int countFrames) :
-      pixelSequence_(pixelSequence)
-    {
-      assert(pixelSequence != NULL);
-
-      startFragment_.resize(countFrames);
-      countFragments_.resize(countFrames);
-      frameSize_.resize(countFrames);
-
-      // The first fragment corresponds to the offset table
-      unsigned int countFragments = static_cast<unsigned int>(pixelSequence_->card());
-      if (countFragments < countFrames + 1)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      if (countFragments == countFrames + 1)
-      {
-        // Simple case: There is one fragment per frame.
-
-        DcmObject* fragment = pixelSequence_->nextInContainer(NULL);  // Skip the offset table
-        if (fragment == NULL)
-        {
-          throw OrthancException(ErrorCode_InternalError);
-        }
-
-        for (unsigned int i = 0; i < countFrames; i++)
-        {
-          fragment = pixelSequence_->nextInContainer(fragment);
-          startFragment_[i] = dynamic_cast<DcmPixelItem*>(fragment);
-          frameSize_[i] = fragment->getLength();
-          countFragments_[i] = 1;
-        }
-
-        return;
-      }
-
-      // Parse the offset table
-      std::vector<uint32_t> offsetOfFrame;
-      GetOffsetTable(offsetOfFrame);
-      
-      if (offsetOfFrame.size() != countFrames ||
-          offsetOfFrame[0] != 0)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-
-      // Loop over the fragments (ignoring the offset table). This is
-      // an alternative, faster implementation to DCMTK's
-      // "DcmCodec::determineStartFragment()".
-      DcmObject* fragment = pixelSequence_->nextInContainer(NULL);
-      if (fragment == NULL)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-
-      fragment = pixelSequence_->nextInContainer(fragment); // Skip the offset table
-      if (fragment == NULL)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-
-      uint32_t offset = 0;
-      unsigned int currentFrame = 0;
-      startFragment_[0] = dynamic_cast<DcmPixelItem*>(fragment);
-
-      unsigned int currentFragment = 1;
-      while (fragment != NULL)
-      {
-        if (currentFrame + 1 < countFrames &&
-            offset == offsetOfFrame[currentFrame + 1])
-        {
-          currentFrame += 1;
-          startFragment_[currentFrame] = dynamic_cast<DcmPixelItem*>(fragment);
-        }
-
-        frameSize_[currentFrame] += fragment->getLength();
-        countFragments_[currentFrame]++;
-
-        // 8 bytes = overhead for the item tag and length field
-        offset += fragment->getLength() + 8;
-
-        currentFragment++;
-        fragment = pixelSequence_->nextInContainer(fragment);
-      }
-
-      if (currentFragment != countFragments ||
-          currentFrame + 1 != countFrames ||
-          fragment != NULL)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      assert(startFragment_.size() == countFragments_.size() &&
-             startFragment_.size() == frameSize_.size());
-    }
-
-
-    virtual void GetRawFrame(std::string& frame,
-                             unsigned int index) const
-    {
-      if (index >= startFragment_.size())
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-
-      frame.resize(frameSize_[index]);
-      if (frame.size() == 0)
-      {
-        return;
-      }
-
-      uint8_t* target = reinterpret_cast<uint8_t*>(&frame[0]);
-
-      size_t offset = 0;
-      DcmPixelItem* fragment = startFragment_[index];
-      for (unsigned int i = 0; i < countFragments_[index]; i++)
-      {
-        uint8_t* content = NULL;
-        if (!fragment->getUint8Array(content).good() ||
-            content == NULL)
-        {
-          throw OrthancException(ErrorCode_InternalError);
-        }
-
-        assert(offset + fragment->getLength() <= frame.size());
-
-        memcpy(target + offset, content, fragment->getLength());
-        offset += fragment->getLength();
-
-        fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment));
-      }
-    }
-  };
-
-
-
-  class DicomFrameIndex::UncompressedIndex : public DicomFrameIndex::IIndex
-  {
-  private:
-    uint8_t*  pixelData_;
-    size_t    frameSize_;
-
-  public: 
-    UncompressedIndex(DcmDataset& dataset,
-                      unsigned int countFrames,
-                      size_t frameSize) :
-      pixelData_(NULL),
-      frameSize_(frameSize)
-    {
-      size_t size = 0;
-
-      DcmElement* e;
-      if (dataset.findAndGetElement(DCM_PixelData, e).good() &&
-          e != NULL)
-      {
-        size = e->getLength();
-
-        if (size > 0)
-        {
-          pixelData_ = NULL;
-          if (!e->getUint8Array(pixelData_).good() ||
-              pixelData_ == NULL)
-          {
-            throw OrthancException(ErrorCode_BadFileFormat);
-          }
-        }
-      }
-
-      if (size < frameSize_ * countFrames)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-    }
-
-    virtual void GetRawFrame(std::string& frame,
-                             unsigned int index) const
-    {
-      frame.resize(frameSize_);
-      if (frameSize_ > 0)
-      {
-        memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_);
-      }
-    }
-  };
-
-
-  class DicomFrameIndex::PsmctRle1Index : public DicomFrameIndex::IIndex
-  {
-  private:
-    std::string  pixelData_;
-    size_t       frameSize_;
-
-  public: 
-    PsmctRle1Index(DcmDataset& dataset,
-                   unsigned int countFrames,
-                   size_t frameSize) :
-      frameSize_(frameSize)
-    {
-      if (!DicomImageDecoder::DecodePsmctRle1(pixelData_, dataset) ||
-          pixelData_.size() < frameSize * countFrames)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-    }
-
-    virtual void GetRawFrame(std::string& frame,
-                             unsigned int index) const
-    {
-      frame.resize(frameSize_);
-      if (frameSize_ > 0)
-      {
-        memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_);
-      }
-    }
-  };
-
-
-
-  bool DicomFrameIndex::IsVideo(DcmFileFormat& dicom)
-  {
-    // Retrieve the transfer syntax from the DICOM header
-    const char* value = NULL;
-    if (!dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() ||
-        value == NULL)
-    {
-      return false;
-    }
-
-    const std::string transferSyntax(value);
-
-    // Video standards supported in DICOM 2016a
-    // http://dicom.nema.org/medical/dicom/2016a/output/html/part05.html
-    if (transferSyntax == "1.2.840.10008.1.2.4.100" ||  // MPEG2 MP@ML option of ISO/IEC MPEG2
-        transferSyntax == "1.2.840.10008.1.2.4.101" ||  // MPEG2 MP@HL option of ISO/IEC MPEG2
-        transferSyntax == "1.2.840.10008.1.2.4.102" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.1 of ITU-T H.264
-        transferSyntax == "1.2.840.10008.1.2.4.103" ||  // MPEG-4 AVC/H.264 BD-compat High Profile / Level 4.1 of ITU-T H.264
-        transferSyntax == "1.2.840.10008.1.2.4.104" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264
-        transferSyntax == "1.2.840.10008.1.2.4.105" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264
-        transferSyntax == "1.2.840.10008.1.2.4.106")    // MPEG-4 AVC/H.264 Stereo High Profile / Level 4.2 of the ITU-T H.264
-    {
-      return true;
-    }
-
-    return false;
-  }
-
-
-  unsigned int DicomFrameIndex::GetFramesCount(DcmFileFormat& dicom)
-  {
-    // Assume 1 frame for video transfer syntaxes
-    if (IsVideo(dicom))
-    {
-      return 1;
-    }        
-
-    const char* tmp = NULL;
-    if (!dicom.getDataset()->findAndGetString(DCM_NumberOfFrames, tmp).good() ||
-        tmp == NULL)
-    {
-      return 1;
-    }
-
-    int count = -1;
-    try
-    {
-      count = boost::lexical_cast<int>(tmp);
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-    }
-
-    if (count < 0)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);        
-    }
-    else
-    {
-      return count;
-    }
-  }
-
-
-  DicomFrameIndex::DicomFrameIndex(DcmFileFormat& dicom)
-  {
-    countFrames_ = GetFramesCount(dicom);
-    if (countFrames_ == 0)
-    {
-      // The image has no frame. No index is to be built.
-      return;
-    }
-
-    DcmDataset& dataset = *dicom.getDataset();
-
-    // Test whether this image is composed of a sequence of fragments
-    DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
-    if (pixelSequence != NULL)
-    {
-      index_.reset(new FragmentIndex(pixelSequence, countFrames_));
-      return;
-    }
-
-    // Extract information about the image structure
-    DicomMap tags;
-    FromDcmtkBridge::ExtractDicomSummary(tags, dataset);
-
-    DicomImageInformation information(tags);
-
-    // Access to the raw pixel data
-    if (DicomImageDecoder::IsPsmctRle1(dataset))
-    {
-      index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize()));
-    }
-    else
-    {
-      index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize()));
-    }
-  }
-
-
-  void DicomFrameIndex::GetRawFrame(std::string& frame,
-                                    unsigned int index) const
-  {
-    if (index >= countFrames_)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-    else if (index_.get() != NULL)
-    {
-      return index_->GetRawFrame(frame, index);
-    }
-    else
-    {
-      frame.clear();
-    }
-  }
-}
--- a/OrthancServer/Internals/DicomFrameIndex.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../../Core/Enumerations.h"
-
-#include <dcmtk/dcmdata/dcdatset.h>
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <vector>
-#include <stdint.h>
-#include <boost/noncopyable.hpp>
-#include <memory>
-
-namespace Orthanc
-{
-  class DicomFrameIndex
-  {
-  private:
-    class IIndex : public boost::noncopyable
-    {
-    public:
-      virtual ~IIndex()
-      {
-      }
-
-      virtual void GetRawFrame(std::string& frame,
-                               unsigned int index) const = 0;
-    };
-
-    class FragmentIndex;
-    class UncompressedIndex;
-    class PsmctRle1Index;
-
-    std::auto_ptr<IIndex>  index_;
-    unsigned int           countFrames_;
-
-  public:
-    DicomFrameIndex(DcmFileFormat& dicom);
-
-    unsigned int GetFramesCount() const
-    {
-      return countFrames_;
-    }
-
-    void GetRawFrame(std::string& frame,
-                     unsigned int index) const;
-
-    static bool IsVideo(DcmFileFormat& dicom);
-
-    static unsigned int GetFramesCount(DcmFileFormat& dicom);
-  };
-}
--- a/OrthancServer/Internals/DicomImageDecoder.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,817 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * 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 "../../Core/Logging.h"
-#include "../../Core/OrthancException.h"
-#include "../../Core/Images/Image.h"
-#include "../../Core/Images/ImageProcessing.h"
-#include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h"
-#include "../ToDcmtkBridge.h"
-#include "../FromDcmtkBridge.h"
-#include "../ParsedDicomFile.h"
-
-#if ORTHANC_ENABLE_PNG == 1
-#  include "../../Core/Images/PngWriter.h"
-#endif
-
-#if ORTHANC_ENABLE_JPEG == 1
-#  include "../../Core/Images/JpegWriter.h"
-#endif
-
-#include <boost/lexical_cast.hpp>
-
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <dcmtk/dcmdata/dcrleccd.h>
-#include <dcmtk/dcmdata/dcrlecp.h>
-
-#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
-#  include <dcmtk/dcmjpls/djcodecd.h>
-#  include <dcmtk/dcmjpls/djcparam.h>
-#  include <dcmtk/dcmjpeg/djrplol.h>
-#endif
-
-#if ORTHANC_ENABLE_DCMTK_JPEG == 1
-#  include <dcmtk/dcmjpeg/djcodecd.h>
-#  include <dcmtk/dcmjpeg/djcparam.h>
-#  include <dcmtk/dcmjpeg/djdecbas.h>
-#  include <dcmtk/dcmjpeg/djdecext.h>
-#  include <dcmtk/dcmjpeg/djdeclol.h>
-#  include <dcmtk/dcmjpeg/djdecpro.h>
-#  include <dcmtk/dcmjpeg/djdecsps.h>
-#  include <dcmtk/dcmjpeg/djdecsv1.h>
-#endif
-
-#if DCMTK_VERSION_NUMBER <= 360
-#  define EXS_JPEGProcess1      EXS_JPEGProcess1TransferSyntax
-#  define EXS_JPEGProcess2_4    EXS_JPEGProcess2_4TransferSyntax
-#  define EXS_JPEGProcess6_8    EXS_JPEGProcess6_8TransferSyntax
-#  define EXS_JPEGProcess10_12  EXS_JPEGProcess10_12TransferSyntax
-#  define EXS_JPEGProcess14     EXS_JPEGProcess14TransferSyntax
-#  define EXS_JPEGProcess14SV1  EXS_JPEGProcess14SV1TransferSyntax
-#endif
-
-namespace Orthanc
-{
-  static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a);
-  static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011);
-
-
-  bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset)
-  {
-    DcmElement* e;
-    char* c;
-
-    // Check whether the DICOM instance contains an image encoded with
-    // the PMSCT_RLE1 scheme.
-    if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_COMPRESSION_TYPE), e).good() ||
-        e == NULL ||
-        !e->isaString() ||
-        !e->getString(c).good() ||
-        c == NULL ||
-        strcmp("PMSCT_RLE1", c))
-    {
-      return false;
-    }
-    else
-    {
-      return true;
-    }
-  }
-
-
-  bool DicomImageDecoder::DecodePsmctRle1(std::string& output,
-                                          DcmDataset& dataset)
-  {
-    // Check whether the DICOM instance contains an image encoded with
-    // the PMSCT_RLE1 scheme.
-    if (!IsPsmctRle1(dataset))
-    {
-      return false;
-    }
-
-    // OK, this is a custom RLE encoding from Philips. Get the pixel
-    // data from the appropriate private DICOM tag.
-    Uint8* pixData = NULL;
-    DcmElement* e;
-    if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_CONTENT), e).good() ||
-        e == NULL ||
-        e->getUint8Array(pixData) != EC_Normal)
-    {
-      return false;
-    }    
-
-    // The "unsigned" below IS VERY IMPORTANT
-    const uint8_t* inbuffer = reinterpret_cast<const uint8_t*>(pixData);
-    const size_t length = e->getLength();
-
-    /**
-     * The code below is an adaptation of a sample code for GDCM by
-     * Mathieu Malaterre (under a BSD license).
-     * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html
-     **/
-
-    // RLE pass
-    std::vector<uint8_t> temp;
-    temp.reserve(length);
-    for (size_t i = 0; i < length; i++)
-    {
-      if (inbuffer[i] == 0xa5)
-      {
-        temp.push_back(inbuffer[i+2]);
-        for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--)
-        {
-          temp.push_back(inbuffer[i+2]);
-        }
-        i += 2;
-      }
-      else
-      {
-        temp.push_back(inbuffer[i]);
-      }
-    }
-
-    // Delta encoding pass
-    uint16_t delta = 0;
-    output.clear();
-    output.reserve(temp.size());
-    for (size_t i = 0; i < temp.size(); i++)
-    {
-      uint16_t value;
-
-      if (temp[i] == 0x5a)
-      {
-        uint16_t v1 = temp[i + 1];
-        uint16_t v2 = temp[i + 2];
-        value = (v2 << 8) + v1;
-        i += 2;
-      }
-      else
-      {
-        value = delta + (int8_t) temp[i];
-      }
-
-      output.push_back(value & 0xff);
-      output.push_back(value >> 8);
-      delta = value;
-    }
-
-    if (output.size() % 2)
-    {
-      output.resize(output.size() - 1);
-    }
-
-    return true;
-  }
-
-
-  class DicomImageDecoder::ImageSource
-  {
-  private:
-    std::string psmct_;
-    std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_;
-
-  public:
-    void Setup(DcmDataset& dataset,
-               unsigned int frame)
-    {
-      psmct_.clear();
-      slowAccessor_.reset(NULL);
-
-      // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data
-
-      DicomMap m;
-      FromDcmtkBridge::ExtractDicomSummary(m, dataset);
-
-      /**
-       * Create an accessor to the raw values of the DICOM image.
-       **/
-
-      DcmElement* e;
-      if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() &&
-          e != NULL)
-      {
-        Uint8* pixData = NULL;
-        if (e->getUint8Array(pixData) == EC_Normal)
-        {    
-          slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength()));
-        }
-      }
-      else if (DecodePsmctRle1(psmct_, dataset))
-      {
-        LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded";
-        Uint8* pixData = NULL;
-        if (psmct_.size() > 0)
-        {
-          pixData = reinterpret_cast<Uint8*>(&psmct_[0]);
-        }
-
-        slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size()));
-      }
-    
-      if (slowAccessor_.get() == NULL)
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      slowAccessor_->SetCurrentFrame(frame);
-    }
-
-    unsigned int GetWidth() const
-    {
-      assert(slowAccessor_.get() != NULL);
-      return slowAccessor_->GetInformation().GetWidth();
-    }
-
-    unsigned int GetHeight() const
-    {
-      assert(slowAccessor_.get() != NULL);
-      return slowAccessor_->GetInformation().GetHeight();
-    }
-
-    unsigned int GetChannelCount() const
-    {
-      assert(slowAccessor_.get() != NULL);
-      return slowAccessor_->GetInformation().GetChannelCount();
-    }
-
-    const DicomIntegerPixelAccessor& GetAccessor() const
-    {
-      assert(slowAccessor_.get() != NULL);
-      return *slowAccessor_;
-    }
-
-    unsigned int GetSize() const
-    {
-      assert(slowAccessor_.get() != NULL);
-      return slowAccessor_->GetSize();
-    }
-  };
-
-
-  ImageAccessor* DicomImageDecoder::CreateImage(DcmDataset& dataset,
-                                                bool ignorePhotometricInterpretation)
-  {
-    DicomMap m;
-    FromDcmtkBridge::ExtractDicomSummary(m, dataset);
-
-    DicomImageInformation info(m);
-    PixelFormat format;
-    
-    if (!info.ExtractPixelFormat(format, ignorePhotometricInterpretation))
-    {
-      LOG(WARNING) << "Unsupported DICOM image: " << info.GetBitsStored() 
-                   << "bpp, " << info.GetChannelCount() << " channels, " 
-                   << (info.IsSigned() ? "signed" : "unsigned")
-                   << (info.IsPlanar() ? ", planar, " : ", non-planar, ")
-                   << EnumerationToString(info.GetPhotometricInterpretation())
-                   << " photometric interpretation";
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-
-    return new Image(format, info.GetWidth(), info.GetHeight(), false);
-  }
-
-
-  template <typename PixelType>
-  static void CopyPixels(ImageAccessor& target,
-                         const DicomIntegerPixelAccessor& source)
-  {
-    const PixelType minValue = std::numeric_limits<PixelType>::min();
-    const PixelType maxValue = std::numeric_limits<PixelType>::max();
-
-    for (unsigned int y = 0; y < source.GetInformation().GetHeight(); y++)
-    {
-      PixelType* pixel = reinterpret_cast<PixelType*>(target.GetRow(y));
-      for (unsigned int x = 0; x < source.GetInformation().GetWidth(); x++)
-      {
-        for (unsigned int c = 0; c < source.GetInformation().GetChannelCount(); c++, pixel++)
-        {
-          int32_t v = source.GetValue(x, y, c);
-          if (v < static_cast<int32_t>(minValue))
-          {
-            *pixel = minValue;
-          }
-          else if (v > static_cast<int32_t>(maxValue))
-          {
-            *pixel = maxValue;
-          }
-          else
-          {
-            *pixel = static_cast<PixelType>(v);
-          }
-        }
-      }
-    }
-  }
-
-
-  ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset,
-                                                            unsigned int frame)
-  {
-    ImageSource source;
-    source.Setup(dataset, frame);
-
-
-    /**
-     * Resize the target image.
-     **/
-
-    std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false));
-
-    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.
-     **/
-
-    const DicomImageInformation& info = source.GetAccessor().GetInformation();
-
-    bool fastVersionSuccess = false;
-    PixelFormat sourceFormat;
-    if (!info.IsPlanar() &&
-        info.ExtractPixelFormat(sourceFormat, false))
-    {
-      try
-      {
-        size_t frameSize = info.GetHeight() * info.GetWidth() * GetBytesPerPixel(sourceFormat);
-        if ((frame + 1) * frameSize <= source.GetSize())
-        {
-          const uint8_t* buffer = reinterpret_cast<const uint8_t*>(source.GetAccessor().GetPixelData());
-
-          ImageAccessor sourceImage;
-          sourceImage.AssignReadOnly(sourceFormat, 
-                                     info.GetWidth(), 
-                                     info.GetHeight(),
-                                     info.GetWidth() * GetBytesPerPixel(sourceFormat),
-                                     buffer + frame * frameSize);
-
-          ImageProcessing::Convert(*target, sourceImage);
-          ImageProcessing::ShiftRight(*target, info.GetShift());
-          fastVersionSuccess = true;
-        }
-      }
-      catch (OrthancException&)
-      {
-        // Unsupported conversion, use the slow version
-      }
-    }
-
-    /**
-     * Slow version : loop over the DICOM buffer, storing its value
-     * into the target image.
-     **/
-
-    if (!fastVersionSuccess)
-    {
-      switch (target->GetFormat())
-      {
-        case PixelFormat_RGB24:
-        case PixelFormat_RGBA32:
-        case PixelFormat_Grayscale8:
-          CopyPixels<uint8_t>(*target, source.GetAccessor());
-          break;
-        
-        case PixelFormat_Grayscale16:
-          CopyPixels<uint16_t>(*target, source.GetAccessor());
-          break;
-
-        case PixelFormat_SignedGrayscale16:
-          CopyPixels<int16_t>(*target, source.GetAccessor());
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-
-    return target.release();
-  }
-
-
-  ImageAccessor* DicomImageDecoder::ApplyCodec(const DcmCodec& codec,
-                                               const DcmCodecParameter& parameters,
-                                               DcmDataset& dataset,
-                                               unsigned int frame)
-  {
-    DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
-    if (pixelSequence == NULL)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true));
-
-    Uint32 startFragment = 0;  // Default 
-    OFString decompressedColorModel;  // Out
-    DJ_RPLossless representationParameter;
-    OFCondition c = codec.decodeFrame(&representationParameter, 
-                                      pixelSequence, &parameters, 
-                                      &dataset, frame, startFragment, target->GetBuffer(), 
-                                      target->GetSize(), decompressedColorModel);
-
-    if (c.good())
-    {
-      return target.release();    
-    }
-    else
-    {
-      LOG(ERROR) << "Cannot decode an image";
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-
-
-  ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom,
-                                           unsigned int frame)
-  {
-    DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset();
-    E_TransferSyntax syntax = dataset.getOriginalXfer();
-
-    /**
-     * Deal with uncompressed, raw images.
-     * http://support.dcmtk.org/docs/dcxfer_8h-source.html
-     **/
-    if (syntax == EXS_Unknown ||
-        syntax == EXS_LittleEndianImplicit ||
-        syntax == EXS_BigEndianImplicit ||
-        syntax == EXS_LittleEndianExplicit ||
-        syntax == EXS_BigEndianExplicit)
-    {
-      return DecodeUncompressedImage(dataset, frame);
-    }
-
-
-#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
-    /**
-     * Deal with JPEG-LS images.
-     **/
-
-    if (syntax == EXS_JPEGLSLossless ||
-        syntax == EXS_JPEGLSLossy)
-    {
-      DJLSCodecParameter parameters;
-      std::auto_ptr<DJLSDecoderBase> decoder;
-
-      switch (syntax)
-      {
-        case EXS_JPEGLSLossless:
-          LOG(INFO) << "Decoding a JPEG-LS lossless DICOM image";
-          decoder.reset(new DJLSLosslessDecoder);
-          break;
-          
-        case EXS_JPEGLSLossy:
-          LOG(INFO) << "Decoding a JPEG-LS near-lossless DICOM image";
-          decoder.reset(new DJLSNearLosslessDecoder);
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    
-      return ApplyCodec(*decoder, parameters, dataset, frame);
-    }
-#endif
-
-
-#if ORTHANC_ENABLE_DCMTK_JPEG == 1
-    /**
-     * Deal with JPEG images.
-     **/
-
-    if (syntax == EXS_JPEGProcess1     ||  // DJDecoderBaseline
-        syntax == EXS_JPEGProcess2_4   ||  // DJDecoderExtended
-        syntax == EXS_JPEGProcess6_8   ||  // DJDecoderSpectralSelection (retired)
-        syntax == EXS_JPEGProcess10_12 ||  // DJDecoderProgressive (retired)
-        syntax == EXS_JPEGProcess14    ||  // DJDecoderLossless
-        syntax == EXS_JPEGProcess14SV1)    // DJDecoderP14SV1
-    {
-      // http://support.dcmtk.org/docs-snapshot/djutils_8h.html#a2a9695e5b6b0f5c45a64c7f072c1eb9d
-      DJCodecParameter parameters(
-        ECC_lossyYCbCr,  // Mode for color conversion for compression, Unused for decompression
-        EDC_photometricInterpretation,  // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr
-        EUC_default,     // Mode for UID creation, unused for decompression
-        EPC_default);    // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation
-      std::auto_ptr<DJCodecDecoder> decoder;
-
-      switch (syntax)
-      {
-        case EXS_JPEGProcess1:
-          LOG(INFO) << "Decoding a JPEG baseline (process 1) DICOM image";
-          decoder.reset(new DJDecoderBaseline);
-          break;
-          
-        case EXS_JPEGProcess2_4 :
-          LOG(INFO) << "Decoding a JPEG baseline (processes 2 and 4) DICOM image";
-          decoder.reset(new DJDecoderExtended);
-          break;
-          
-        case EXS_JPEGProcess6_8:   // Retired
-          LOG(INFO) << "Decoding a JPEG spectral section, nonhierarchical (processes 6 and 8) DICOM image";
-          decoder.reset(new DJDecoderSpectralSelection);
-          break;
-          
-        case EXS_JPEGProcess10_12:   // Retired
-          LOG(INFO) << "Decoding a JPEG full progression, nonhierarchical (processes 10 and 12) DICOM image";
-          decoder.reset(new DJDecoderProgressive);
-          break;
-          
-        case EXS_JPEGProcess14:
-          LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical (process 14) DICOM image";
-          decoder.reset(new DJDecoderLossless);
-          break;
-          
-        case EXS_JPEGProcess14SV1:
-          LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical, first-order prediction (process 14 selection value 1) DICOM image";
-          decoder.reset(new DJDecoderP14SV1);
-          break;
-          
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    
-      return ApplyCodec(*decoder, parameters, dataset, frame);      
-    }
-#endif
-
-
-    if (syntax == EXS_RLELossless)
-    {
-      LOG(INFO) << "Decoding a RLE lossless DICOM image";
-      DcmRLECodecParameter parameters;
-      DcmRLECodecDecoder decoder;
-      return ApplyCodec(decoder, parameters, dataset, frame);
-    }
-
-
-    /**
-     * This DICOM image format is not natively supported by
-     * Orthanc. As a last resort, try and decode it through DCMTK by
-     * converting its transfer syntax to Little Endian. This will
-     * result in higher memory consumption. This is actually the
-     * second example of the following page:
-     * http://support.dcmtk.org/docs/mod_dcmjpeg.html#Examples
-     **/
-    
-    {
-      LOG(INFO) << "Decoding a compressed image by converting its transfer syntax to Little Endian";
-
-      std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone()));
-      converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
-
-      if (converted->canWriteXfer(EXS_LittleEndianExplicit))
-      {
-        return DecodeUncompressedImage(*converted, frame);
-      }
-    }
-
-    LOG(ERROR) << "Cannot decode a DICOM image with the built-in decoder";
-    throw OrthancException(ErrorCode_BadFileFormat);
-  }
-
-
-  static bool IsColorImage(PixelFormat format)
-  {
-    return (format == PixelFormat_RGB24 ||
-            format == PixelFormat_RGBA32);
-  }
-
-
-  bool DicomImageDecoder::TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image,
-                                               PixelFormat format,
-                                               bool allowColorConversion)
-  {
-    // If specified, prevent the conversion between color and
-    // grayscale images
-    bool isSourceColor = IsColorImage(image->GetFormat());
-    bool isTargetColor = IsColorImage(format);
-
-    if (!allowColorConversion)
-    {
-      if (isSourceColor ^ isTargetColor)
-      {
-        return false;
-      }
-    }
-
-    if (image->GetFormat() != format)
-    {
-      // A conversion is required
-      std::auto_ptr<ImageAccessor> target(new Image(format, image->GetWidth(), image->GetHeight(), false));
-      ImageProcessing::Convert(*target, *image);
-      image = target;
-    }
-
-    return true;
-  }
-
-
-  bool DicomImageDecoder::PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image)
-  {
-    switch (image->GetFormat())
-    {
-      case PixelFormat_RGB24:
-      {
-        // Directly return color images without modification (RGB)
-        return true;
-      }
-
-      case PixelFormat_Grayscale8:
-      case PixelFormat_Grayscale16:
-      case PixelFormat_SignedGrayscale16:
-      {
-        // Grayscale image: Stretch its dynamics to the [0,255] range
-        int64_t a, b;
-        ImageProcessing::GetMinMaxValue(a, b, *image);
-
-        if (a == b)
-        {
-          ImageProcessing::Set(*image, 0);
-        }
-        else
-        {
-          ImageProcessing::ShiftScale(*image, static_cast<float>(-a), 255.0f / static_cast<float>(b - a));
-        }
-
-        // If the source image is not grayscale 8bpp, convert it
-        if (image->GetFormat() != PixelFormat_Grayscale8)
-        {
-          std::auto_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false));
-          ImageProcessing::Convert(*target, *image);
-          image = target;
-        }
-
-        return true;
-      }
-      
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image,
-                                              ImageExtractionMode mode,
-                                              bool invert)
-  {
-    if (image.get() == NULL)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    bool ok = false;
-
-    switch (mode)
-    {
-      case ImageExtractionMode_UInt8:
-        ok = TruncateDecodedImage(image, PixelFormat_Grayscale8, false);
-        break;
-
-      case ImageExtractionMode_UInt16:
-        ok = TruncateDecodedImage(image, PixelFormat_Grayscale16, false);
-        break;
-
-      case ImageExtractionMode_Int16:
-        ok = TruncateDecodedImage(image, PixelFormat_SignedGrayscale16, false);
-        break;
-
-      case ImageExtractionMode_Preview:
-        ok = PreviewDecodedImage(image);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    if (ok)
-    {
-      assert(image.get() != NULL);
-
-      if (invert)
-      {
-        Orthanc::ImageProcessing::Invert(*image);
-      }
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-#if ORTHANC_ENABLE_PNG == 1
-  void DicomImageDecoder::ExtractPngImage(std::string& result,
-                                          std::auto_ptr<ImageAccessor>& image,
-                                          ImageExtractionMode mode,
-                                          bool invert)
-  {
-    ApplyExtractionMode(image, mode, invert);
-
-    PngWriter writer;
-    writer.WriteToMemory(result, *image);
-  }
-#endif
-
-
-#if ORTHANC_ENABLE_JPEG == 1
-  void DicomImageDecoder::ExtractJpegImage(std::string& result,
-                                           std::auto_ptr<ImageAccessor>& image,
-                                           ImageExtractionMode mode,
-                                           bool invert,
-                                           uint8_t quality)
-  {
-    if (mode != ImageExtractionMode_UInt8 &&
-        mode != ImageExtractionMode_Preview)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    ApplyExtractionMode(image, mode, invert);
-
-    JpegWriter writer;
-    writer.SetQuality(quality);
-    writer.WriteToMemory(result, *image);
-  }
-#endif
-}
--- a/OrthancServer/Internals/DicomImageDecoder.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../ParsedDicomFile.h"
-
-#include <memory>
-
-#if !defined(ORTHANC_ENABLE_JPEG)
-#  error The macro ORTHANC_ENABLE_JPEG must be defined
-#endif
-
-#if !defined(ORTHANC_ENABLE_PNG)
-#  error The macro ORTHANC_ENABLE_PNG must be defined
-#endif
-
-#if !defined(ORTHANC_ENABLE_DCMTK_JPEG)
-#  error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined
-#endif
-
-#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS)
-#  error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined
-#endif
-
-
-class DcmDataset;
-class DcmCodec;
-class DcmCodecParameter;
-
-namespace Orthanc
-{
-  class DicomImageDecoder : public boost::noncopyable
-  {
-  private:
-    class ImageSource;
-
-    DicomImageDecoder()   // This is a fully abstract class, no constructor
-    {
-    }
-
-    static ImageAccessor* CreateImage(DcmDataset& dataset,
-                                      bool ignorePhotometricInterpretation);
-
-    static ImageAccessor* DecodeUncompressedImage(DcmDataset& dataset,
-                                                  unsigned int frame);
-
-    static ImageAccessor* ApplyCodec(const DcmCodec& codec,
-                                     const DcmCodecParameter& parameters,
-                                     DcmDataset& dataset,
-                                     unsigned int frame);
-
-    static bool TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image,
-                                     PixelFormat format,
-                                     bool allowColorConversion);
-
-    static bool PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image);
-
-    static void ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image,
-                                    ImageExtractionMode mode,
-                                    bool invert);
-
-  public:
-    static bool IsPsmctRle1(DcmDataset& dataset);
-
-    static bool DecodePsmctRle1(std::string& output,
-                                DcmDataset& dataset);
-
-    static ImageAccessor *Decode(ParsedDicomFile& dicom,
-                                 unsigned int frame);
-
-#if ORTHANC_ENABLE_PNG == 1
-    static void ExtractPngImage(std::string& result,
-                                std::auto_ptr<ImageAccessor>& image,
-                                ImageExtractionMode mode,
-                                bool invert);
-#endif
-
-#if ORTHANC_ENABLE_JPEG == 1
-    static void ExtractJpegImage(std::string& result,
-                                 std::auto_ptr<ImageAccessor>& image,
-                                 ImageExtractionMode mode,
-                                 bool invert,
-                                 uint8_t quality);
-#endif
-  };
-}
--- a/OrthancServer/Internals/FindScp.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,349 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-
-/*=========================================================================
-
-  This file is based on portions of the following project:
-
-  Program: DCMTK 3.6.0
-  Module:  http://dicom.offis.de/dcmtk.php.en
-
-Copyright (C) 1994-2011, OFFIS e.V.
-All rights reserved.
-
-This software and supporting documentation were developed by
-
-  OFFIS e.V.
-  R&D Division Health
-  Escherweg 2
-  26121 Oldenburg, Germany
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-- Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-- Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-- Neither the name of OFFIS nor the names of its contributors may be
-  used to endorse or promote products derived from this software
-  without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-=========================================================================*/
-
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "FindScp.h"
-
-#include "../FromDcmtkBridge.h"
-#include "../ToDcmtkBridge.h"
-#include "../../Core/Logging.h"
-#include "../../Core/OrthancException.h"
-#include "../OrthancInitialization.h"
-
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <dcmtk/dcmdata/dcdeftag.h>
-
-
-
-/**
- * The function below is extracted from DCMTK 3.6.0, cf. file
- * "dcmtk-3.6.0/dcmwlm/libsrc/wldsfs.cc".
- **/
-
-static void HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(DcmDataset *dataset, 
-                                                                             const DcmTagKey &sequenceTagKey)
-// Date         : May 3, 2005
-// Author       : Thomas Wilkens
-// Task         : This function performs a check on a sequence attribute in the given dataset. At two different places
-//                in the definition of the DICOM worklist management service, a sequence attribute with a return type
-//                of 2 is mentioned containing two 1C attributes in its item; the condition of the two 1C attributes
-//                specifies that in case a sequence item is present, then these two attributes must be existent and
-//                must contain a value. (I am talking about ReferencedStudySequence and ReferencedPatientSequence.)
-//                In cases where the sequence attribute contains exactly one item with an empty ReferencedSOPClass
-//                and an empty ReferencedSOPInstance, we want to remove the item from the sequence. This is what
-//                this function does.
-// Parameters   : dataset         - [in] Dataset in which the consistency of the sequence attribute shall be checked.
-//                sequenceTagKey  - [in] DcmTagKey of the sequence attribute which shall be checked.
-// Return Value : none.
-{
-  DcmElement *sequenceAttribute = NULL, *referencedSOPClassUIDAttribute = NULL, *referencedSOPInstanceUIDAttribute = NULL;
-
-  // in case the sequence attribute contains exactly one item with an empty
-  // ReferencedSOPClassUID and an empty ReferencedSOPInstanceUID, remove the item
-  if( dataset->findAndGetElement( sequenceTagKey, sequenceAttribute ).good() &&
-      ( (DcmSequenceOfItems*)sequenceAttribute )->card() == 1 &&
-      ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute ).good() &&
-      referencedSOPClassUIDAttribute->getLength() == 0 &&
-      ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse ).good() &&
-      referencedSOPInstanceUIDAttribute->getLength() == 0 )
-  {
-    DcmItem *item = ((DcmSequenceOfItems*)sequenceAttribute)->remove( ((DcmSequenceOfItems*)sequenceAttribute)->getItem(0) );
-    delete item;
-  }
-}
-
-
-
-namespace Orthanc
-{
-  namespace
-  {  
-    struct FindScpData
-    {
-      DicomServer::IRemoteModalities* modalities_;
-      IFindRequestHandler* findHandler_;
-      IWorklistRequestHandler* worklistHandler_;
-      DicomFindAnswers answers_;
-      DcmDataset* lastRequest_;
-      const std::string* remoteIp_;
-      const std::string* remoteAet_;
-      const std::string* calledAet_;
-
-      FindScpData() : answers_(false)
-      {
-      }
-    };
-
-
-
-    static void FixWorklistQuery(ParsedDicomFile& query)
-    {
-      // TODO: Check out
-      // WlmDataSourceFileSystem::HandleExistentButEmptyDescriptionAndCodeSequenceAttributes()"
-      // in DCMTK 3.6.0
-
-      DcmDataset* dataset = query.GetDcmtkObject().getDataset();      
-      HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedStudySequence);
-      HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedPatientSequence);
-    }
-
-
-
-    void FindScpCallback(
-      /* in */ 
-      void *callbackData,  
-      OFBool cancelled, 
-      T_DIMSE_C_FindRQ *request, 
-      DcmDataset *requestIdentifiers, 
-      int responseCount,
-      /* out */
-      T_DIMSE_C_FindRSP *response,
-      DcmDataset **responseIdentifiers,
-      DcmDataset **statusDetail)
-    {
-      bzero(response, sizeof(T_DIMSE_C_FindRSP));
-      *statusDetail = NULL;
-
-      std::string sopClassUid(request->AffectedSOPClassUID);
-
-      FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData);
-      if (data.lastRequest_ == NULL)
-      {
-        bool ok = false;
-
-        try
-        {
-          RemoteModalityParameters modality;
-
-          /**
-           * Ensure that the remote modality is known to Orthanc for C-FIND requests.
-           **/
-
-          assert(data.modalities_ != NULL);
-          if (!data.modalities_->LookupAETitle(modality, *data.remoteAet_))
-          {
-            LOG(ERROR) << "Modality with AET \"" << *data.remoteAet_
-                       << "\" is not defined in the \"DicomModalities\" configuration option";
-            throw OrthancException(ErrorCode_UnknownModality);
-          }
-
-          
-          if (sopClassUid == UID_FINDModalityWorklistInformationModel)
-          {
-            data.answers_.SetWorklist(true);
-
-            if (data.worklistHandler_ != NULL)
-            {
-              ParsedDicomFile query(*requestIdentifiers);
-              FixWorklistQuery(query);
-
-              data.worklistHandler_->Handle(data.answers_, query,
-                                            *data.remoteIp_, *data.remoteAet_,
-                                            *data.calledAet_, modality.GetManufacturer());
-              ok = true;
-            }
-            else
-            {
-              LOG(ERROR) << "No worklist handler is installed, cannot handle this C-FIND request";
-            }
-          }
-          else
-          {
-            data.answers_.SetWorklist(false);
-
-            if (data.findHandler_ != NULL)
-            {
-              std::list<DicomTag> sequencesToReturn;
-
-              for (unsigned long i = 0; i < requestIdentifiers->card(); i++)
-              {
-                DcmElement* element = requestIdentifiers->getElement(i);
-                if (element && !element->isLeaf())
-                {
-                  const DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
-
-                  DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
-                  if (sequence.card() != 0)
-                  {
-                    LOG(WARNING) << "Orthanc only supports sequence matching on worklists, "
-                                 << "ignoring C-FIND SCU constraint on tag (" << tag.Format() 
-                                 << ") " << FromDcmtkBridge::GetTagName(*element);
-                  }
-
-                  sequencesToReturn.push_back(tag);
-                }
-              }
-
-              DicomMap input;
-              FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers);
-
-              data.findHandler_->Handle(data.answers_, input, sequencesToReturn,
-                                        *data.remoteIp_, *data.remoteAet_,
-                                        *data.calledAet_, modality.GetManufacturer());
-              ok = true;
-            }
-            else
-            {
-              LOG(ERROR) << "No C-Find handler is installed, cannot handle this request";
-            }
-          }
-        }
-        catch (OrthancException& e)
-        {
-          // Internal error!
-          LOG(ERROR) <<  "C-FIND request handler has failed: " << e.What();
-        }
-
-        if (!ok)
-        {
-          response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
-          *responseIdentifiers = NULL;   
-          return;
-        }
-
-        data.lastRequest_ = requestIdentifiers;
-      }
-      else if (data.lastRequest_ != requestIdentifiers)
-      {
-        // Internal error!
-        response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
-        *responseIdentifiers = NULL;   
-        return;
-      }
-
-      if (responseCount <= static_cast<int>(data.answers_.GetSize()))
-      {
-        // There are pending results that are still to be sent
-        response->DimseStatus = STATUS_Pending;
-        *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1);
-      }
-      else if (data.answers_.IsComplete())
-      {
-        // Success: All the results have been sent
-        response->DimseStatus = STATUS_Success;
-        *responseIdentifiers = NULL;
-      }
-      else
-      {
-        // Success, but the results were too numerous and had to be cropped
-        LOG(WARNING) <<  "Too many results for an incoming C-FIND query";
-        response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest;
-        *responseIdentifiers = NULL;
-      }
-    }
-  }
-
-
-  OFCondition Internals::findScp(T_ASC_Association * assoc, 
-                                 T_DIMSE_Message * msg, 
-                                 T_ASC_PresentationContextID presID,
-                                 DicomServer::IRemoteModalities& modalities,
-                                 IFindRequestHandler* findHandler,
-                                 IWorklistRequestHandler* worklistHandler,
-                                 const std::string& remoteIp,
-                                 const std::string& remoteAet,
-                                 const std::string& calledAet)
-  {
-    FindScpData data;
-    data.modalities_ = &modalities;
-    data.findHandler_ = findHandler;
-    data.worklistHandler_ = worklistHandler;
-    data.lastRequest_ = NULL;
-    data.remoteIp_ = &remoteIp;
-    data.remoteAet_ = &remoteAet;
-    data.calledAet_ = &calledAet;
-
-    OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, 
-                                          FindScpCallback, &data,
-                                          /*opt_blockMode*/ DIMSE_BLOCKING, 
-                                          /*opt_dimse_timeout*/ 0);
-
-    // if some error occured, dump corresponding information and remove the outfile if necessary
-    if (cond.bad())
-    {
-      OFString temp_str;
-      LOG(ERROR) << "Find SCP Failed: " << cond.text();
-    }
-
-    return cond;
-  }
-}
--- a/OrthancServer/Internals/FindScp.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../DicomProtocol/DicomServer.h"
-
-#include <dcmtk/dcmnet/dimse.h>
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    OFCondition findScp(T_ASC_Association * assoc, 
-                        T_DIMSE_Message * msg, 
-                        T_ASC_PresentationContextID presID,
-                        DicomServer::IRemoteModalities& modalities,
-                        IFindRequestHandler* findHandler,   // can be NULL
-                        IWorklistRequestHandler* worklistHandler,   // can be NULL
-                        const std::string& remoteIp,
-                        const std::string& remoteAet,
-                        const std::string& calledAet);
-  }
-}
--- a/OrthancServer/Internals/MoveScp.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,283 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-
-
-/*=========================================================================
-
-  This file is based on portions of the following project:
-
-  Program: DCMTK 3.6.0
-  Module:  http://dicom.offis.de/dcmtk.php.en
-
-Copyright (C) 1994-2011, OFFIS e.V.
-All rights reserved.
-
-This software and supporting documentation were developed by
-
-  OFFIS e.V.
-  R&D Division Health
-  Escherweg 2
-  26121 Oldenburg, Germany
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-- Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-- Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-- Neither the name of OFFIS nor the names of its contributors may be
-  used to endorse or promote products derived from this software
-  without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-=========================================================================*/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "MoveScp.h"
-
-#include <memory>
-
-#include "../FromDcmtkBridge.h"
-#include "../ToDcmtkBridge.h"
-#include "../../Core/Logging.h"
-#include "../../Core/OrthancException.h"
-
-#include <boost/lexical_cast.hpp>
-
-
-namespace Orthanc
-{
-  namespace
-  {  
-    struct MoveScpData
-    {
-      std::string target_;
-      IMoveRequestHandler* handler_;
-      DcmDataset* lastRequest_;
-      unsigned int subOperationCount_;
-      unsigned int failureCount_;
-      unsigned int warningCount_;
-      std::auto_ptr<IMoveRequestIterator> iterator_;
-      const std::string* remoteIp_;
-      const std::string* remoteAet_;
-      const std::string* calledAet_;
-    };
-
-
-
-    static uint16_t GetMessageId(const DicomMap& message)
-    {
-      /**
-       * Retrieve the Message ID (0000,0110) for this C-MOVE request, if
-       * any. If present, this Message ID will be stored in the Move
-       * Originator Message ID (0000,1031) field of the C-MOVE response.
-       * http://dicom.nema.org/dicom/2013/output/chtml/part07/chapter_E.html
-       **/
-
-      const DicomValue* value = message.TestAndGetValue(DICOM_TAG_MESSAGE_ID);
-
-      if (value != NULL &&
-          !value->IsNull() &&
-          !value->IsBinary())
-      {
-        try
-        {
-          int tmp = boost::lexical_cast<int>(value->GetContent());
-          if (tmp >= 0 && tmp <= 0xffff)
-          {
-            return static_cast<uint16_t>(tmp);
-          }
-        }
-        catch (boost::bad_lexical_cast&)
-        {
-          LOG(WARNING) << "Cannot convert the Message ID (\"" << value->GetContent()
-                       << "\") of an incoming C-MOVE request to an integer, assuming zero";
-        }
-      }
-
-      return 0;
-    }
-
-
-
-    void MoveScpCallback(
-      /* in */ 
-      void *callbackData,  
-      OFBool cancelled, 
-      T_DIMSE_C_MoveRQ *request, 
-      DcmDataset *requestIdentifiers, 
-      int responseCount,
-      /* out */
-      T_DIMSE_C_MoveRSP *response,
-      DcmDataset **responseIdentifiers,
-      DcmDataset **statusDetail)
-    {
-      bzero(response, sizeof(T_DIMSE_C_MoveRSP));
-      *statusDetail = NULL;
-      *responseIdentifiers = NULL;   
-
-      MoveScpData& data = *reinterpret_cast<MoveScpData*>(callbackData);
-      if (data.lastRequest_ == NULL)
-      {
-        DicomMap input;
-        FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers);
-
-        try
-        {
-          data.iterator_.reset(data.handler_->Handle(data.target_, input, *data.remoteIp_, *data.remoteAet_,
-                                                     *data.calledAet_, GetMessageId(input)));
-
-          if (data.iterator_.get() == NULL)
-          {
-            // Internal error!
-            response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess;
-            return;
-          }
-
-          data.subOperationCount_ = data.iterator_->GetSubOperationCount();
-          data.failureCount_ = 0;
-          data.warningCount_ = 0;
-        }
-        catch (OrthancException& e)
-        {
-          // Internal error!
-          LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What();
-          response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess;
-          return;
-        }
-
-        data.lastRequest_ = requestIdentifiers;
-      }
-      else if (data.lastRequest_ != requestIdentifiers)
-      {
-        // Internal error!
-        response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess;
-        return;
-      }
-  
-      if (data.subOperationCount_ == 0)
-      {
-        response->DimseStatus = STATUS_Success;
-      }
-      else
-      {
-        IMoveRequestIterator::Status status;
-
-        try
-        {
-          status = data.iterator_->DoNext();
-        }
-        catch (OrthancException& e)
-        {
-          // Internal error!
-          LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What();
-          response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess;
-          return;
-        }
-
-        if (status == IMoveRequestIterator::Status_Failure)
-        {
-          data.failureCount_++;
-        }
-        else if (status == IMoveRequestIterator::Status_Warning)
-        {
-          data.warningCount_++;
-        }
-
-        if (responseCount < static_cast<int>(data.subOperationCount_))
-        {
-          response->DimseStatus = STATUS_Pending;
-        }
-        else
-        {
-          response->DimseStatus = STATUS_Success;
-        }
-      }
-
-      response->NumberOfRemainingSubOperations = data.subOperationCount_ - responseCount;
-      response->NumberOfCompletedSubOperations = responseCount;
-      response->NumberOfFailedSubOperations = data.failureCount_;
-      response->NumberOfWarningSubOperations = data.warningCount_;
-    }
-  }
-
-
-  OFCondition Internals::moveScp(T_ASC_Association * assoc, 
-                                 T_DIMSE_Message * msg, 
-                                 T_ASC_PresentationContextID presID,
-                                 IMoveRequestHandler& handler,
-                                 const std::string& remoteIp,
-                                 const std::string& remoteAet,
-                                 const std::string& calledAet)
-  {
-    MoveScpData data;
-    data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination);
-    data.lastRequest_ = NULL;
-    data.handler_ = &handler;
-    data.remoteIp_ = &remoteIp;
-    data.remoteAet_ = &remoteAet;
-    data.calledAet_ = &calledAet;
-
-    OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, 
-                                          MoveScpCallback, &data,
-                                          /*opt_blockMode*/ DIMSE_BLOCKING, 
-                                          /*opt_dimse_timeout*/ 0);
-
-    // if some error occured, dump corresponding information and remove the outfile if necessary
-    if (cond.bad())
-    {
-      OFString temp_str;
-      LOG(ERROR) << "Move SCP Failed: " << cond.text();
-    }
-
-    return cond;
-  }
-}
--- a/OrthancServer/Internals/MoveScp.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../DicomProtocol/IMoveRequestHandler.h"
-
-#include <dcmtk/dcmnet/dimse.h>
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    OFCondition moveScp(T_ASC_Association * assoc, 
-                        T_DIMSE_Message * msg, 
-                        T_ASC_PresentationContextID presID,
-                        IMoveRequestHandler& handler,
-                        const std::string& remoteIp,
-                        const std::string& remoteAet,
-                        const std::string& calledAet);
-  }
-}
--- a/OrthancServer/Internals/StoreScp.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,298 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-
-
-/*=========================================================================
-
-  This file is based on portions of the following project:
-
-  Program: DCMTK 3.6.0
-  Module:  http://dicom.offis.de/dcmtk.php.en
-
-Copyright (C) 1994-2011, OFFIS e.V.
-All rights reserved.
-
-This software and supporting documentation were developed by
-
-  OFFIS e.V.
-  R&D Division Health
-  Escherweg 2
-  26121 Oldenburg, Germany
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-- Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-
-- Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-- Neither the name of OFFIS nor the names of its contributors may be
-  used to endorse or promote products derived from this software
-  without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-=========================================================================*/
-
-
-#include "../PrecompiledHeadersServer.h"
-#include "StoreScp.h"
-
-#include "../FromDcmtkBridge.h"
-#include "../ToDcmtkBridge.h"
-#include "../../Core/OrthancException.h"
-#include "../../Core/Logging.h"
-
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <dcmtk/dcmdata/dcmetinf.h>
-#include <dcmtk/dcmdata/dcostrmb.h>
-#include <dcmtk/dcmdata/dcdeftag.h>
-#include <dcmtk/dcmnet/diutil.h>
-
-
-namespace Orthanc
-{
-  namespace
-  {  
-    struct StoreCallbackData
-    {
-      IStoreRequestHandler* handler;
-      const std::string* remoteIp;
-      const char* remoteAET;
-      const char* calledAET;
-      const char* modality;
-      const char* affectedSOPInstanceUID;
-      uint32_t messageID;
-    };
-
-    
-    static void
-    storeScpCallback(
-      void *callbackData,
-      T_DIMSE_StoreProgress *progress,
-      T_DIMSE_C_StoreRQ *req,
-      char * /*imageFileName*/, DcmDataset **imageDataSet,
-      T_DIMSE_C_StoreRSP *rsp,
-      DcmDataset **statusDetail)
-    /*
-     * This function.is used to indicate progress when storescp receives instance data over the
-     * network. On the final call to this function (identified by progress->state == DIMSE_StoreEnd)
-     * this function will store the data set which was received over the network to a file.
-     * Earlier calls to this function will simply cause some information to be dumped to stdout.
-     *
-     * Parameters:
-     *   callbackData  - [in] data for this callback function
-     *   progress      - [in] The state of progress. (identifies if this is the initial or final call
-     *                   to this function, or a call in between these two calls.
-     *   req           - [in] The original store request message.
-     *   imageFileName - [in] The path to and name of the file the information shall be written to.
-     *   imageDataSet  - [in] The data set which shall be stored in the image file
-     *   rsp           - [inout] the C-STORE-RSP message (will be sent after the call to this function)
-     *   statusDetail  - [inout] This variable can be used to capture detailed information with regard to
-     *                   the status information which is captured in the status element (0000,0900). Note
-     *                   that this function does specify any such information, the pointer will be set to NULL.
-     */
-    {
-      StoreCallbackData *cbdata = OFstatic_cast(StoreCallbackData *, callbackData);
-
-      DIC_UI sopClass;
-      DIC_UI sopInstance;
-
-      // if this is the final call of this function, save the data which was received to a file
-      // (note that we could also save the image somewhere else, put it in database, etc.)
-      if (progress->state == DIMSE_StoreEnd)
-      {
-        OFString tmpStr;
-
-        // do not send status detail information
-        *statusDetail = NULL;
-
-        // Concerning the following line: an appropriate status code is already set in the resp structure,
-        // it need not be success. For example, if the caller has already detected an out of resources problem
-        // then the status will reflect this.  The callback function is still called to allow cleanup.
-        //rsp->DimseStatus = STATUS_Success;
-
-        // we want to write the received information to a file only if this information
-        // is present and the options opt_bitPreserving and opt_ignore are not set.
-        if ((imageDataSet != NULL) && (*imageDataSet != NULL))
-        {
-          DicomMap summary;
-          Json::Value dicomJson;
-          std::string buffer;
-
-          try
-          {
-            FromDcmtkBridge::ExtractDicomSummary(summary, **imageDataSet);
-            FromDcmtkBridge::ExtractDicomAsJson(dicomJson, **imageDataSet);
-
-            if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet))
-            {
-              LOG(ERROR) << "cannot write DICOM file to memory";
-              rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
-            }
-          }
-          catch (...)
-          {
-            rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
-          }
-
-          // check the image to make sure it is consistent, i.e. that its sopClass and sopInstance correspond
-          // to those mentioned in the request. If not, set the status in the response message variable.
-          if (rsp->DimseStatus == STATUS_Success)
-          {
-            // which SOP class and SOP instance ?
-            if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sopInstance, /*opt_correctUIDPadding*/ OFFalse))
-            {
-              //LOG4CPP_ERROR(Internals::GetLogger(), "bad DICOM file: " << fileName);
-              rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand;
-            }
-            else if (strcmp(sopClass, req->AffectedSOPClassUID) != 0)
-            {
-              rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
-            }
-            else if (strcmp(sopInstance, req->AffectedSOPInstanceUID) != 0)
-            {
-              rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
-            }
-            else
-            {
-              try
-              {
-                cbdata->handler->Handle(buffer, summary, dicomJson, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET);
-              }
-              catch (OrthancException& e)
-              {
-                rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
-
-                if (e.GetErrorCode() == ErrorCode_InexistentTag)
-                {
-                  summary.LogMissingTagsForStore();
-                }
-                else
-                {
-                  LOG(ERROR) << "Exception while storing DICOM: " << e.What();
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-
-/*
- * This function processes a DIMSE C-STORE-RQ commmand that was
- * received over the network connection.
- *
- * Parameters:
- *   assoc  - [in] The association (network connection to another DICOM application).
- *   msg    - [in] The DIMSE C-STORE-RQ message that was received.
- *   presID - [in] The ID of the presentation context which was specified in the PDV which contained
- *                 the DIMSE command.
- */
-  OFCondition Internals::storeScp(T_ASC_Association * assoc, 
-                                  T_DIMSE_Message * msg, 
-                                  T_ASC_PresentationContextID presID,
-                                  IStoreRequestHandler& handler,
-                                  const std::string& remoteIp)
-  {
-    OFCondition cond = EC_Normal;
-    T_DIMSE_C_StoreRQ *req;
-
-    // assign the actual information of the C-STORE-RQ command to a local variable
-    req = &msg->msg.CStoreRQ;
-
-    // intialize some variables
-    StoreCallbackData data;
-    data.handler = &handler;
-    data.remoteIp = &remoteIp;
-    data.modality = dcmSOPClassUIDToModality(req->AffectedSOPClassUID/*, "UNKNOWN"*/);
-    if (data.modality == NULL)
-      data.modality = "UNKNOWN";
-
-    data.affectedSOPInstanceUID = req->AffectedSOPInstanceUID;
-    data.messageID = req->MessageID;
-    if (assoc && assoc->params)
-    {
-      data.remoteAET = assoc->params->DULparams.callingAPTitle;
-      data.calledAET = assoc->params->DULparams.calledAPTitle;
-    }
-    else
-    {
-      data.remoteAET = "";
-      data.calledAET = "";
-    }
-
-    DcmFileFormat dcmff;
-
-    // store SourceApplicationEntityTitle in metaheader
-    if (assoc && assoc->params)
-    {
-      const char *aet = assoc->params->DULparams.callingAPTitle;
-      if (aet) dcmff.getMetaInfo()->putAndInsertString(DCM_SourceApplicationEntityTitle, aet);
-    }
-
-    // define an address where the information which will be received over the network will be stored
-    DcmDataset *dset = dcmff.getDataset();
-
-    cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset,
-                               storeScpCallback, &data, 
-                               /*opt_blockMode*/ DIMSE_BLOCKING, 
-                               /*opt_dimse_timeout*/ 0);
-
-    // if some error occured, dump corresponding information and remove the outfile if necessary
-    if (cond.bad())
-    {
-      OFString temp_str;
-      LOG(ERROR) << "Store SCP Failed: " << cond.text();
-    }
-
-    // return return value
-    return cond;
-  }
-}
--- a/OrthancServer/Internals/StoreScp.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "../DicomProtocol/IStoreRequestHandler.h"
-
-#include <dcmtk/dcmnet/dimse.h>
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    OFCondition storeScp(T_ASC_Association * assoc, 
-                         T_DIMSE_Message * msg, 
-                         T_ASC_PresentationContextID presID,
-                         IStoreRequestHandler& handler,
-                         const std::string& remoteIp);
-  }
-}
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -37,7 +37,7 @@
 #include "../Core/DicomFormat/DicomArray.h"
 #include "../Core/Lua/LuaFunctionCall.h"
 #include "../Core/Logging.h"
-#include "FromDcmtkBridge.h"
+#include "../Core/DicomParsing/FromDcmtkBridge.h"
 #include "OrthancInitialization.h"
 #include "Search/LookupResource.h"
 #include "ServerToolbox.h"
--- a/OrthancServer/OrthancFindRequestHandler.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.h	Tue Aug 29 21:17:35 2017 +0200
@@ -32,7 +32,7 @@
 
 #pragma once
 
-#include "DicomProtocol/IFindRequestHandler.h"
+#include "../Core/DicomNetworking/IFindRequestHandler.h"
 
 #include "ServerContext.h"
 
--- a/OrthancServer/OrthancInitialization.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -42,7 +42,7 @@
 
 #include "ServerEnumerations.h"
 #include "DatabaseWrapper.h"
-#include "FromDcmtkBridge.h"
+#include "../Core/DicomParsing/FromDcmtkBridge.h"
 
 #include <boost/lexical_cast.hpp>
 #include <boost/filesystem.hpp>
--- a/OrthancServer/OrthancInitialization.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancInitialization.h	Tue Aug 29 21:17:35 2017 +0200
@@ -42,8 +42,8 @@
 #include "../Core/HttpServer/MongooseServer.h"
 #include "../Core/Images/FontRegistry.h"
 #include "../Core/WebServiceParameters.h"
+#include "../Core/DicomNetworking/RemoteModalityParameters.h"
 
-#include "DicomProtocol/RemoteModalityParameters.h"
 #include "IDatabaseWrapper.h"
 #include "ServerEnumerations.h"
 
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -35,7 +35,7 @@
 #include "OrthancMoveRequestHandler.h"
 
 #include "OrthancInitialization.h"
-#include "FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../Core/DicomFormat/DicomArray.h"
 #include "../Core/Logging.h"
 
--- a/OrthancServer/OrthancMoveRequestHandler.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancMoveRequestHandler.h	Tue Aug 29 21:17:35 2017 +0200
@@ -32,7 +32,7 @@
 
 #pragma once
 
-#include "DicomProtocol/IMoveRequestHandler.h"
+#include "../Core/DicomNetworking/IMoveRequestHandler.h"
 #include "ServerContext.h"
 
 namespace Orthanc
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -35,7 +35,7 @@
 #include "OrthancRestApi.h"
 
 #include "../../Core/Logging.h"
-#include "../FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../ServerContext.h"
 #include "../OrthancInitialization.h"
 
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -35,7 +35,6 @@
 #include "OrthancRestApi.h"
 
 #include "../../Core/Logging.h"
-#include "../DicomModification.h"
 #include "../ServerContext.h"
 
 namespace Orthanc
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h	Tue Aug 29 21:17:35 2017 +0200
@@ -34,7 +34,8 @@
 #pragma once
 
 #include "../../Core/RestApi/RestApi.h"
-#include "../DicomModification.h"
+#include "../../Core/DicomParsing/DicomModification.h"
+#include "../ServerEnumerations.h"
 
 #include <set>
 
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -34,7 +34,7 @@
 #include "../PrecompiledHeadersServer.h"
 #include "OrthancRestApi.h"
 
-#include "../DicomDirWriter.h"
+#include "../../Core/DicomParsing/DicomDirWriter.h"
 #include "../../Core/FileStorage/StorageAccessor.h"
 #include "../../Core/Compression/HierarchicalZipWriter.h"
 #include "../../Core/HttpServer/FilesystemHttpSender.h"
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -37,7 +37,7 @@
 #include "../OrthancInitialization.h"
 #include "../../Core/HttpClient.h"
 #include "../../Core/Logging.h"
-#include "../FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../Scheduler/ServerJob.h"
 #include "../Scheduler/StoreScuCommand.h"
 #include "../Scheduler/StorePeerCommand.h"
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -36,8 +36,8 @@
 
 #include "../../Core/HttpServer/HttpContentNegociation.h"
 #include "../../Core/Logging.h"
-#include "../FromDcmtkBridge.h"
-#include "../Internals/DicomImageDecoder.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/Internals/DicomImageDecoder.h"
 #include "../OrthancInitialization.h"
 #include "../Search/LookupResource.h"
 #include "../ServerContext.h"
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -35,7 +35,7 @@
 #include "OrthancRestApi.h"
 
 #include "../OrthancInitialization.h"
-#include "../FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../../Plugins/Engine/PluginsManager.h"
 #include "../../Plugins/Engine/OrthancPlugins.h"
 #include "../ServerContext.h"
--- a/OrthancServer/ParsedDicomFile.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1476 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * 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/DicomFrameIndex.h"
-#include "../Core/Logging.h"
-#include "../Core/OrthancException.h"
-#include "../Core/Toolbox.h"
-#include "../Core/SystemToolbox.h"
-
-#if ORTHANC_ENABLE_JPEG == 1
-#  include "../Core/Images/JpegReader.h"
-#endif
-
-#if ORTHANC_ENABLE_PNG == 1
-#  include "../Core/Images/PngReader.h"
-#endif
-
-#include <list>
-#include <limits>
-
-#include <boost/lexical_cast.hpp>
-
-#include <dcmtk/dcmdata/dcchrstr.h>
-#include <dcmtk/dcmdata/dcdicent.h>
-#include <dcmtk/dcmdata/dcdict.h>
-#include <dcmtk/dcmdata/dcfilefo.h>
-#include <dcmtk/dcmdata/dcuid.h>
-#include <dcmtk/dcmdata/dcmetinf.h>
-#include <dcmtk/dcmdata/dcdeftag.h>
-
-#include <dcmtk/dcmdata/dcvrae.h>
-#include <dcmtk/dcmdata/dcvras.h>
-#include <dcmtk/dcmdata/dcvrcs.h>
-#include <dcmtk/dcmdata/dcvrda.h>
-#include <dcmtk/dcmdata/dcvrds.h>
-#include <dcmtk/dcmdata/dcvrdt.h>
-#include <dcmtk/dcmdata/dcvrfd.h>
-#include <dcmtk/dcmdata/dcvrfl.h>
-#include <dcmtk/dcmdata/dcvris.h>
-#include <dcmtk/dcmdata/dcvrlo.h>
-#include <dcmtk/dcmdata/dcvrlt.h>
-#include <dcmtk/dcmdata/dcvrpn.h>
-#include <dcmtk/dcmdata/dcvrsh.h>
-#include <dcmtk/dcmdata/dcvrsl.h>
-#include <dcmtk/dcmdata/dcvrss.h>
-#include <dcmtk/dcmdata/dcvrst.h>
-#include <dcmtk/dcmdata/dcvrtm.h>
-#include <dcmtk/dcmdata/dcvrui.h>
-#include <dcmtk/dcmdata/dcvrul.h>
-#include <dcmtk/dcmdata/dcvrus.h>
-#include <dcmtk/dcmdata/dcvrut.h>
-#include <dcmtk/dcmdata/dcpixel.h>
-#include <dcmtk/dcmdata/dcpixseq.h>
-#include <dcmtk/dcmdata/dcpxitem.h>
-
-
-#include <boost/math/special_functions/round.hpp>
-#include <dcmtk/dcmdata/dcostrmb.h>
-#include <boost/algorithm/string/predicate.hpp>
-
-
-#if DCMTK_VERSION_NUMBER <= 360
-#  define EXS_JPEGProcess1      EXS_JPEGProcess1TransferSyntax
-#endif
-
-
-static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
-
-
-
-namespace Orthanc
-{
-  struct ParsedDicomFile::PImpl
-  {
-    std::auto_ptr<DcmFileFormat> file_;
-    std::auto_ptr<DicomFrameIndex>  frameIndex_;
-  };
-
-
-  static void SendPathValueForDictionary(RestApiOutput& output,
-                                         DcmItem& dicom)
-  {
-    Json::Value v = Json::arrayValue;
-
-    for (unsigned long i = 0; i < dicom.card(); i++)
-    {
-      DcmElement* element = dicom.getElement(i);
-      if (element)
-      {
-        char buf[16];
-        sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag());
-        v.append(buf);
-      }
-    }
-
-    output.AnswerJson(v);
-  }
-
-  static inline uint16_t GetCharValue(char c)
-  {
-    if (c >= '0' && c <= '9')
-      return c - '0';
-    else if (c >= 'a' && c <= 'f')
-      return c - 'a' + 10;
-    else if (c >= 'A' && c <= 'F')
-      return c - 'A' + 10;
-    else
-      return 0;
-  }
-
-  static inline uint16_t GetTagValue(const char* c)
-  {
-    return ((GetCharValue(c[0]) << 12) + 
-            (GetCharValue(c[1]) << 8) + 
-            (GetCharValue(c[2]) << 4) + 
-            GetCharValue(c[3]));
-  }
-
-  static void ParseTagAndGroup(DcmTagKey& key,
-                               const std::string& tag)
-  {
-    DicomTag t = FromDcmtkBridge::ParseTag(tag);
-    key = DcmTagKey(t.GetGroup(), t.GetElement());
-  }
-
-
-  static void SendSequence(RestApiOutput& output,
-                           DcmSequenceOfItems& sequence)
-  {
-    // This element is a sequence
-    Json::Value v = Json::arrayValue;
-
-    for (unsigned long i = 0; i < sequence.card(); i++)
-    {
-      v.append(boost::lexical_cast<std::string>(i));
-    }
-
-    output.AnswerJson(v);
-  }
-
-
-  static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData,
-                                             E_TransferSyntax transferSyntax)
-  {
-    DcmPixelSequence* pixelSequence = NULL;
-    if (pixelData.getEncapsulatedRepresentation
-        (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
-    {
-      return pixelSequence->card();
-    }
-    else
-    {
-      return 1;
-    }
-  }
-
-
-  namespace
-  {
-    class DicomFieldStream : public IHttpStreamAnswer
-    {
-    private:
-      DcmElement&  element_;
-      uint32_t     length_;
-      uint32_t     offset_;
-      std::string  chunk_;
-      size_t       chunkSize_;
-      
-    public:
-      DicomFieldStream(DcmElement& element,
-                       E_TransferSyntax transferSyntax) :
-        element_(element),
-        length_(element.getLength(transferSyntax)),
-        offset_(0),
-        chunkSize_(0)
-      {
-        static const size_t CHUNK_SIZE = 64 * 1024;  // Use chunks of max 64KB
-        chunk_.resize(CHUNK_SIZE);
-      }
-
-      virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/,
-                                                   bool /*deflateAllowed*/)
-      {
-        // No support for compression
-        return HttpCompression_None;
-      }
-
-      virtual bool HasContentFilename(std::string& filename)
-      {
-        return false;
-      }
-
-      virtual std::string GetContentType()
-      {
-        return "";
-      }
-
-      virtual uint64_t  GetContentLength()
-      {
-        return length_;
-      }
- 
-      virtual bool ReadNextChunk()
-      {
-        assert(offset_ <= length_);
-
-        if (offset_ == length_)
-        {
-          return false;
-        }
-        else
-        {
-          if (length_ - offset_ < chunk_.size())
-          {
-            chunkSize_ = length_ - offset_;
-          }
-          else
-          {
-            chunkSize_ = chunk_.size();
-          }
-
-          OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_);
-
-          offset_ += chunkSize_;
-
-          if (!cond.good())
-          {
-            LOG(ERROR) << "Error while sending a DICOM field: " << cond.text();
-            throw OrthancException(ErrorCode_InternalError);
-          }
-
-          return true;
-        }
-      }
- 
-      virtual const char *GetChunkContent()
-      {
-        return chunk_.c_str();
-      }
- 
-      virtual size_t GetChunkSize()
-      {
-        return chunkSize_;
-      }
-    };
-  }
-
-
-  static bool AnswerPixelData(RestApiOutput& output,
-                              DcmItem& dicom,
-                              E_TransferSyntax transferSyntax,
-                              const std::string* blockUri)
-  {
-    DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(),
-             DICOM_TAG_PIXEL_DATA.GetElement());
-
-    DcmElement *element = NULL;
-    if (!dicom.findAndGetElement(k, element).good() ||
-        element == NULL)
-    {
-      return false;
-    }
-
-    try
-    {
-      DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
-      if (blockUri == NULL)
-      {
-        // The user asks how many blocks are present in this pixel data
-        unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax);
-
-        Json::Value result(Json::arrayValue);
-        for (unsigned int i = 0; i < blocks; i++)
-        {
-          result.append(boost::lexical_cast<std::string>(i));
-        }
-        
-        output.AnswerJson(result);
-        return true;
-      }
-
-
-      unsigned int block = boost::lexical_cast<unsigned int>(*blockUri);
-
-      if (block < GetPixelDataBlockCount(pixelData, transferSyntax))
-      {
-        DcmPixelSequence* pixelSequence = NULL;
-        if (pixelData.getEncapsulatedRepresentation
-            (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
-        {
-          // This is the case for JPEG transfer syntaxes
-          if (block < pixelSequence->card())
-          {
-            DcmPixelItem* pixelItem = NULL;
-            if (pixelSequence->getItem(pixelItem, block).good() && pixelItem)
-            {
-              if (pixelItem->getLength() == 0)
-              {
-                output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM);
-                return true;
-              }
-
-              Uint8* buffer = NULL;
-              if (pixelItem->getUint8Array(buffer).good() && buffer)
-              {
-                output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM);
-                return true;
-              }
-            }
-          }
-        }
-        else
-        {
-          // This is the case for raw, uncompressed image buffers
-          assert(*blockUri == "0");
-          DicomFieldStream stream(*element, transferSyntax);
-          output.AnswerStream(stream);
-        }
-      }
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      // The URI entered by the user is not a number
-    }
-    catch (std::bad_cast&)
-    {
-      // This should never happen
-    }
-
-    return false;
-  }
-
-
-
-  static void SendPathValueForLeaf(RestApiOutput& output,
-                                   const std::string& tag,
-                                   DcmItem& dicom,
-                                   E_TransferSyntax transferSyntax)
-  {
-    DcmTagKey k;
-    ParseTagAndGroup(k, tag);
-
-    DcmSequenceOfItems* sequence = NULL;
-    if (dicom.findAndGetSequence(k, sequence).good() && 
-        sequence != NULL &&
-        sequence->getVR() == EVR_SQ)
-    {
-      SendSequence(output, *sequence);
-      return;
-    }
-
-    DcmElement* element = NULL;
-    if (dicom.findAndGetElement(k, element).good() && 
-        element != NULL &&
-        //element->getVR() != EVR_UNKNOWN &&  // This would forbid private tags
-        element->getVR() != EVR_SQ)
-    {
-      DicomFieldStream stream(*element, transferSyntax);
-      output.AnswerStream(stream);
-    }
-  }
-
-  void ParsedDicomFile::SendPathValue(RestApiOutput& output,
-                                      const UriComponents& uri)
-  {
-    DcmItem* dicom = pimpl_->file_->getDataset();
-    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
-
-    // Special case: Accessing the pixel data
-    if (uri.size() == 1 || 
-        uri.size() == 2)
-    {
-      DcmTagKey tag;
-      ParseTagAndGroup(tag, uri[0]);
-
-      if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() &&
-          tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement())
-      {
-        AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]);
-        return;
-      }
-    }        
-
-    // Go down in the tag hierarchy according to the URI
-    for (size_t pos = 0; pos < uri.size() / 2; pos++)
-    {
-      size_t index;
-      try
-      {
-        index = boost::lexical_cast<size_t>(uri[2 * pos + 1]);
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        return;
-      }
-
-      DcmTagKey k;
-      DcmItem *child = NULL;
-      ParseTagAndGroup(k, uri[2 * pos]);
-      if (!dicom->findAndGetSequenceItem(k, child, index).good() ||
-          child == NULL)
-      {
-        return;
-      }
-
-      dicom = child;
-    }
-
-    // We have reached the end of the URI
-    if (uri.size() % 2 == 0)
-    {
-      SendPathValueForDictionary(output, *dicom);
-    }
-    else
-    {
-      SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax);
-    }
-  }
-
-
-  void ParsedDicomFile::Remove(const DicomTag& tag)
-  {
-    InvalidateCache();
-
-    DcmTagKey key(tag.GetGroup(), tag.GetElement());
-    DcmElement* element = pimpl_->file_->getDataset()->remove(key);
-    if (element != NULL)
-    {
-      delete element;
-    }
-  }
-
-
-  void ParsedDicomFile::Clear(const DicomTag& tag,
-                              bool onlyIfExists)
-  {
-    InvalidateCache();
-
-    DcmItem* dicom = pimpl_->file_->getDataset();
-    DcmTagKey key(tag.GetGroup(), tag.GetElement());
-
-    if (onlyIfExists &&
-        !dicom->tagExists(key))
-    {
-      // The tag is non-existing, do not clear it
-    }
-    else
-    {
-      if (!dicom->insertEmptyElement(key, OFTrue /* replace old value */).good())
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-  }
-
-
-  void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep)
-  {
-    InvalidateCache();
-
-    DcmDataset& dataset = *pimpl_->file_->getDataset();
-
-    // Loop over the dataset to detect its private tags
-    typedef std::list<DcmElement*> Tags;
-    Tags privateTags;
-
-    for (unsigned long i = 0; i < dataset.card(); i++)
-    {
-      DcmElement* element = dataset.getElement(i);
-      DcmTag tag(element->getTag());
-
-      // Is this a private tag?
-      if (tag.isPrivate())
-      {
-        bool remove = true;
-
-        // Check whether this private tag is to be kept
-        if (toKeep != NULL)
-        {
-          DicomTag tmp = FromDcmtkBridge::Convert(tag);
-          if (toKeep->find(tmp) != toKeep->end())
-          {
-            remove = false;  // Keep it
-          }
-        }
-            
-        if (remove)
-        {
-          privateTags.push_back(element);
-        }
-      }
-    }
-
-    // Loop over the detected private tags to remove them
-    for (Tags::iterator it = privateTags.begin(); 
-         it != privateTags.end(); ++it)
-    {
-      DcmElement* tmp = dataset.remove(*it);
-      if (tmp != NULL)
-      {
-        delete tmp;
-      }
-    }
-  }
-
-
-  static void InsertInternal(DcmDataset& dicom,
-                             DcmElement* element)
-  {
-    OFCondition cond = dicom.insert(element, false, false);
-    if (!cond.good())
-    {
-      // This field already exists
-      delete element;
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-
-
-  void ParsedDicomFile::Insert(const DicomTag& tag,
-                               const Json::Value& value,
-                               bool decodeDataUriScheme)
-  {
-    if (pimpl_->file_->getDataset()->tagExists(ToDcmtkBridge::Convert(tag)))
-    {
-      throw OrthancException(ErrorCode_AlreadyExistingTag);
-    }
-
-    if (decodeDataUriScheme &&
-        value.type() == Json::stringValue &&
-        (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
-         tag == DICOM_TAG_PIXEL_DATA))
-    {
-      if (EmbedContentInternal(value.asString()))
-      {
-        return;
-      }
-    }
-
-    InvalidateCache();
-    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
-    InsertInternal(*pimpl_->file_->getDataset(), element.release());
-  }
-
-
-  static bool CanReplaceProceed(DcmDataset& dicom,
-                                const DcmTagKey& tag,
-                                DicomReplaceMode mode)
-  {
-    if (dicom.findAndDeleteElement(tag).good())
-    {
-      // This tag was existing, it has been deleted
-      return true;
-    }
-    else
-    {
-      // This tag was absent, act wrt. the specified "mode"
-      switch (mode)
-      {
-        case DicomReplaceMode_InsertIfAbsent:
-          return true;
-
-        case DicomReplaceMode_ThrowIfAbsent:
-          throw OrthancException(ErrorCode_InexistentItem);
-
-        case DicomReplaceMode_IgnoreIfAbsent:
-          return false;
-
-        default:
-          throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-    }
-  }
-
-
-  void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag,
-                                         const std::string& utf8Value,
-                                         bool decodeDataUriScheme)
-  {
-    if (tag != DICOM_TAG_SOP_CLASS_UID &&
-        tag != DICOM_TAG_SOP_INSTANCE_UID)
-    {
-      return;
-    }
-
-    std::string binary;
-    const std::string* decoded = &utf8Value;
-
-    if (decodeDataUriScheme &&
-        boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
-    {
-      std::string mime;
-      if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value))
-      {
-        throw OrthancException(ErrorCode_BadFileFormat);
-      }
-
-      decoded = &binary;
-    }
-    else
-    {
-      Encoding encoding = GetEncoding();
-      if (GetEncoding() != Encoding_Utf8)
-      {
-        binary = Toolbox::ConvertFromUtf8(utf8Value, encoding);
-        decoded = &binary;
-      }
-    }
-
-    /**
-     * dcmodify will automatically correct 'Media Storage SOP Class
-     * UID' and 'Media Storage SOP Instance UID' in the metaheader, if
-     * you make changes to the related tags in the dataset ('SOP Class
-     * UID' and 'SOP Instance UID') via insert or modify mode
-     * options. You can disable this behaviour by using the -nmu
-     * option.
-     **/
-
-    if (tag == DICOM_TAG_SOP_CLASS_UID)
-    {
-      ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded);
-    }
-
-    if (tag == DICOM_TAG_SOP_INSTANCE_UID)
-    {
-      ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded);
-    }    
-  }
-
-
-  void ParsedDicomFile::Replace(const DicomTag& tag,
-                                const std::string& utf8Value,
-                                bool decodeDataUriScheme,
-                                DicomReplaceMode mode)
-  {
-    InvalidateCache();
-
-    DcmDataset& dicom = *pimpl_->file_->getDataset();
-    if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
-    {
-      // Either the tag was previously existing (and now removed), or
-      // the replace mode was set to "InsertIfAbsent"
-
-      if (decodeDataUriScheme &&
-          (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
-           tag == DICOM_TAG_PIXEL_DATA))
-      {
-        if (EmbedContentInternal(utf8Value))
-        {
-          return;
-        }
-      }
-
-      std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
-      FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, GetEncoding());
-
-      InsertInternal(dicom, element.release());
-      UpdateStorageUid(tag, utf8Value, false);
-    }
-  }
-
-    
-  void ParsedDicomFile::Replace(const DicomTag& tag,
-                                const Json::Value& value,
-                                bool decodeDataUriScheme,
-                                DicomReplaceMode mode)
-  {
-    InvalidateCache();
-
-    DcmDataset& dicom = *pimpl_->file_->getDataset();
-    if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
-    {
-      // Either the tag was previously existing (and now removed), or
-      // the replace mode was set to "InsertIfAbsent"
-
-      if (decodeDataUriScheme &&
-          value.type() == Json::stringValue &&
-          (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
-           tag == DICOM_TAG_PIXEL_DATA))
-      {
-        if (EmbedContentInternal(value.asString()))
-        {
-          return;
-        }
-      }
-
-      InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
-
-      if (tag == DICOM_TAG_SOP_CLASS_UID ||
-          tag == DICOM_TAG_SOP_INSTANCE_UID)
-      {
-        if (value.type() != Json::stringValue)
-        {
-          throw OrthancException(ErrorCode_BadParameterType);
-        }
-
-        UpdateStorageUid(tag, value.asString(), decodeDataUriScheme);
-      }
-    }
-  }
-
-    
-  void ParsedDicomFile::Answer(RestApiOutput& output)
-  {
-    std::string serialized;
-    if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *pimpl_->file_->getDataset()))
-    {
-      output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM);
-    }
-  }
-
-
-
-  bool ParsedDicomFile::GetTagValue(std::string& value,
-                                    const DicomTag& tag)
-  {
-    DcmTagKey k(tag.GetGroup(), tag.GetElement());
-    DcmDataset& dataset = *pimpl_->file_->getDataset();
-
-    if (tag.IsPrivate() ||
-        FromDcmtkBridge::IsUnknownTag(tag) ||
-        tag == DICOM_TAG_PIXEL_DATA ||
-        tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
-    {
-      const Uint8* data = NULL;   // This is freed in the destructor of the dataset
-      long unsigned int count = 0;
-
-      if (dataset.findAndGetUint8Array(k, data, &count).good())
-      {
-        if (count > 0)
-        {
-          assert(data != NULL);
-          value.assign(reinterpret_cast<const char*>(data), count);
-        }
-        else
-        {
-          value.clear();
-        }
-
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-    else
-    {
-      DcmElement* element = NULL;
-      if (!dataset.findAndGetElement(k, element).good() ||
-          element == NULL)
-      {
-        return false;
-      }
-
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
-                                  (*element, DicomToJsonFlags_Default, 
-                                   ORTHANC_MAXIMUM_TAG_LENGTH, GetEncoding()));
-      
-      if (v.get() == NULL ||
-          v->IsNull())
-      {
-        value = "";
-      }
-      else
-      {
-        // TODO v->IsBinary()
-        value = v->GetContent();
-      }
-      
-      return true;
-    }
-  }
-
-
-  DicomInstanceHasher ParsedDicomFile::GetHasher()
-  {
-    std::string patientId, studyUid, seriesUid, instanceUid;
-
-    if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) ||
-        !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) ||
-        !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) ||
-        !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID))
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid);
-  }
-
-
-  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);
-    SystemToolbox::WriteFile(content, path);
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl)
-  {
-    pimpl_->file_.reset(new DcmFileFormat);
-
-    if (createIdentifiers)
-    {
-      ReplacePlainString(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
-      ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
-      ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
-      ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
-    }
-  }
-
-
-  void ParsedDicomFile::CreateFromDicomMap(const DicomMap& source,
-                                           Encoding defaultEncoding)
-  {
-    pimpl_->file_.reset(new DcmFileFormat);
-
-    const DicomValue* tmp = source.TestAndGetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET);
-    if (tmp != NULL)
-    {
-      Encoding encoding;
-      if (tmp->IsNull() ||
-          tmp->IsBinary() ||
-          !GetDicomEncoding(encoding, tmp->GetContent().c_str()))
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-      }
-      else
-      {
-        SetEncoding(encoding);
-      }
-    }
-    else
-    {
-      SetEncoding(defaultEncoding);
-    }
-
-    for (DicomMap::Map::const_iterator 
-           it = source.map_.begin(); it != source.map_.end(); ++it)
-    {
-      if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET &&
-          !it->second->IsNull())
-      {
-        ReplacePlainString(it->first, it->second->GetContent());
-      }
-    }
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(const DicomMap& map,
-                                   Encoding defaultEncoding) :
-    pimpl_(new PImpl)
-  {
-    CreateFromDicomMap(map, defaultEncoding);
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(const DicomMap& map) : 
-    pimpl_(new PImpl)
-  {
-    CreateFromDicomMap(map, GetDefaultDicomEncoding());
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(const void* content, 
-                                   size_t size) : pimpl_(new PImpl)
-  {
-    pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(content, size));
-  }
-
-  ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl)
-  {
-    if (content.size() == 0)
-    {
-      pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(NULL, 0));
-    }
-    else
-    {
-      pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(&content[0], content.size()));
-    }
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other) : 
-    pimpl_(new PImpl)
-  {
-    pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone()));
-
-    // Create a new instance-level identifier
-    ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl)
-  {
-    pimpl_->file_.reset(new DcmFileFormat(&dicom));
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl)
-  {
-    pimpl_->file_.reset(new DcmFileFormat(dicom));
-  }
-
-
-  ParsedDicomFile::~ParsedDicomFile()
-  {
-    delete pimpl_;
-  }
-
-
-  DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const
-  {
-    return *pimpl_->file_.get();
-  }
-
-
-  ParsedDicomFile* ParsedDicomFile::Clone()
-  {
-    return new ParsedDicomFile(*this);
-  }
-
-
-  bool ParsedDicomFile::EmbedContentInternal(const std::string& dataUriScheme)
-  {
-    std::string mime, content;
-    if (!Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme))
-    {
-      return false;
-    }
-
-    Toolbox::ToLowerCase(mime);
-
-    if (mime == "image/png")
-    {
-#if ORTHANC_ENABLE_PNG == 1
-      EmbedImage(mime, content);
-#else
-      LOG(ERROR) << "Orthanc was compiled without support of PNG";
-      throw OrthancException(ErrorCode_NotImplemented);
-#endif
-    }
-    else if (mime == "image/jpeg")
-    {
-#if ORTHANC_ENABLE_JPEG == 1
-      EmbedImage(mime, content);
-#else
-      LOG(ERROR) << "Orthanc was compiled without support of JPEG";
-      throw OrthancException(ErrorCode_NotImplemented);
-#endif
-    }
-    else if (mime == "application/pdf")
-    {
-      EmbedPdf(content);
-    }
-    else
-    {
-      LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime;
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-
-    return true;
-  }
-
-
-  void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme)
-  {
-    if (!EmbedContentInternal(dataUriScheme))
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-
-
-#if (ORTHANC_ENABLE_JPEG == 1 &&  \
-     ORTHANC_ENABLE_PNG == 1)
-  void ParsedDicomFile::EmbedImage(const std::string& mime,
-                                   const std::string& content)
-  {
-    if (mime == "image/png")
-    {
-      PngReader reader;
-      reader.ReadFromMemory(content);
-      EmbedImage(reader);
-    }
-    else if (mime == "image/jpeg")
-    {
-      JpegReader reader;
-      reader.ReadFromMemory(content);
-      EmbedImage(reader);
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-#endif
-
-
-  void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor)
-  {
-    if (accessor.GetFormat() != PixelFormat_Grayscale8 &&
-        accessor.GetFormat() != PixelFormat_Grayscale16 &&
-        accessor.GetFormat() != PixelFormat_SignedGrayscale16 &&
-        accessor.GetFormat() != PixelFormat_RGB24 &&
-        accessor.GetFormat() != PixelFormat_RGBA32)
-    {
-      throw OrthancException(ErrorCode_NotImplemented);
-    }
-
-    InvalidateCache();
-
-    if (accessor.GetFormat() == PixelFormat_RGBA32)
-    {
-      LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM";
-    }
-
-    // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html
-
-    Remove(DICOM_TAG_PIXEL_DATA);
-    ReplacePlainString(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth()));
-    ReplacePlainString(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight()));
-    ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "1");
-    ReplacePlainString(DICOM_TAG_NUMBER_OF_FRAMES, "1");
-
-    if (accessor.GetFormat() == PixelFormat_SignedGrayscale16)
-    {
-      ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "1");
-    }
-    else
-    {
-      ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "0");  // Unsigned pixels
-    }
-
-    ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0");  // Color channels are interleaved
-    ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2");
-
-    unsigned int bytesPerPixel = 0;
-
-    switch (accessor.GetFormat())
-    {
-      case PixelFormat_Grayscale8:
-        ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8");
-        ReplacePlainString(DICOM_TAG_BITS_STORED, "8");
-        ReplacePlainString(DICOM_TAG_HIGH_BIT, "7");
-        bytesPerPixel = 1;
-        break;
-
-      case PixelFormat_RGB24:
-      case PixelFormat_RGBA32:
-        ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB");
-        ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3");
-        ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8");
-        ReplacePlainString(DICOM_TAG_BITS_STORED, "8");
-        ReplacePlainString(DICOM_TAG_HIGH_BIT, "7");
-        bytesPerPixel = 3;
-        break;
-
-      case PixelFormat_Grayscale16:
-      case PixelFormat_SignedGrayscale16:
-        ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16");
-        ReplacePlainString(DICOM_TAG_BITS_STORED, "16");
-        ReplacePlainString(DICOM_TAG_HIGH_BIT, "15");
-        bytesPerPixel = 2;
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-
-    assert(bytesPerPixel != 0);
-
-    DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), 
-               DICOM_TAG_PIXEL_DATA.GetElement());
-
-    std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key));
-
-    unsigned int pitch = accessor.GetWidth() * bytesPerPixel;
-    Uint8* target = NULL;
-    pixels->createUint8Array(accessor.GetHeight() * pitch, target);
-
-    for (unsigned int y = 0; y < accessor.GetHeight(); y++)
-    {
-      switch (accessor.GetFormat())
-      {
-        case PixelFormat_RGB24:
-        case PixelFormat_Grayscale8:
-        case PixelFormat_Grayscale16:
-        case PixelFormat_SignedGrayscale16:
-        {
-          memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch);
-          target += pitch;
-          break;
-        }
-
-        case PixelFormat_RGBA32:
-        {
-          // The alpha channel is not supported by the DICOM standard
-          const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y));
-          for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4)
-          {
-            target[0] = source[0];
-            target[1] = source[1];
-            target[2] = source[2];
-          }
-
-          break;
-        }
-          
-        default:
-          throw OrthancException(ErrorCode_NotImplemented);
-      }
-    }
-
-    if (!pimpl_->file_->getDataset()->insert(pixels.release(), false, false).good())
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }    
-  }
-
-  
-  Encoding ParsedDicomFile::GetEncoding() const
-  {
-    return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset(),
-                                           GetDefaultDicomEncoding());
-  }
-
-
-  void ParsedDicomFile::SetEncoding(Encoding encoding)
-  {
-    if (encoding == Encoding_Windows1251)
-    {
-      // This Cyrillic codepage is not officially supported by the
-      // DICOM standard. Do not set the SpecificCharacterSet tag.
-      return;
-    }
-
-    std::string s = GetDicomSpecificCharacterSet(encoding);
-    ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s);
-  }
-
-  void ParsedDicomFile::DatasetToJson(Json::Value& target, 
-                                      DicomToJsonFormat format,
-                                      DicomToJsonFlags flags,
-                                      unsigned int maxStringLength)
-  {
-    FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(),
-                                        format, flags, maxStringLength, GetDefaultDicomEncoding());
-  }
-
-
-  void ParsedDicomFile::DatasetToJson(Json::Value& target)
-  {
-    FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset());
-  }
-
-
-  void ParsedDicomFile::HeaderToJson(Json::Value& target, 
-                                     DicomToJsonFormat format)
-  {
-    FromDcmtkBridge::ExtractHeaderAsJson(target, *pimpl_->file_->getMetaInfo(), format, DicomToJsonFlags_None, 0);
-  }
-
-
-  bool ParsedDicomFile::HasTag(const DicomTag& tag) const
-  {
-    DcmTag key(tag.GetGroup(), tag.GetElement());
-    return pimpl_->file_->getDataset()->tagExists(key);
-  }
-
-
-  void ParsedDicomFile::EmbedPdf(const std::string& pdf)
-  {
-    if (pdf.size() < 5 ||  // (*)
-        strncmp("%PDF-", pdf.c_str(), 5) != 0)
-    {
-      LOG(ERROR) << "Not a PDF file";
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    InvalidateCache();
-
-    ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
-    ReplacePlainString(FromDcmtkBridge::Convert(DCM_Modality), "OT");
-    ReplacePlainString(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
-    ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf");
-    //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
-
-    std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument));
-
-    size_t s = pdf.size();
-    if (s & 1)
-    {
-      // The size of the buffer must be even
-      s += 1;
-    }
-
-    Uint8* bytes = NULL;
-    OFCondition result = element->createUint8Array(s, bytes);
-    if (!result.good() || bytes == NULL)
-    {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-
-    // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) )
-    bytes[s - 1] = 0;
-
-    memcpy(bytes, pdf.c_str(), pdf.size());
-      
-    DcmPolymorphOBOW* obj = element.release();
-    result = pimpl_->file_->getDataset()->insert(obj);
-
-    if (!result.good())
-    {
-      delete obj;
-      throw OrthancException(ErrorCode_NotEnoughMemory);
-    }
-  }
-
-
-  bool ParsedDicomFile::ExtractPdf(std::string& pdf)
-  {
-    std::string sop, mime;
-    
-    if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) ||
-        !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) ||
-        sop != UID_EncapsulatedPDFStorage ||
-        mime != "application/pdf")
-    {
-      return false;
-    }
-
-    if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT))
-    {
-      return false;
-    }
-
-    // Strip the possible pad byte at the end of file, because the
-    // encapsulated documents must always have an even length. The PDF
-    // format expects files to end with %%EOF followed by CR/LF. If
-    // the last character of the file is not a CR or LF, we assume it
-    // is a pad byte and remove it.
-    if (pdf.size() > 0)
-    {
-      char last = *pdf.rbegin();
-
-      if (last != 10 && last != 13)
-      {
-        pdf.resize(pdf.size() - 1);
-      }
-    }
-
-    return true;
-  }
-
-
-  ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json,
-                                                   DicomFromJsonFlags flags)
-  {
-	const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false;
-	const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false;
-
-    std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers));
-    result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding()));
-
-    const Json::Value::Members tags = json.getMemberNames();
-    
-    for (size_t i = 0; i < tags.size(); i++)
-    {
-      DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
-      const Json::Value& value = json[tags[i]];
-
-      if (tag == DICOM_TAG_PIXEL_DATA ||
-          tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
-      {
-        if (value.type() != Json::stringValue)
-        {
-          throw OrthancException(ErrorCode_BadRequest);
-        }
-        else
-        {
-          result->EmbedContent(value.asString());
-        }
-      }
-      else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
-      {
-        result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent);
-      }
-    }
-
-    return result.release();
-  }
-
-
-  void ParsedDicomFile::GetRawFrame(std::string& target,
-                                    std::string& mime,
-                                    unsigned int frameId)
-  {
-    if (pimpl_->frameIndex_.get() == NULL)
-    {
-      pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_));
-    }
-
-    pimpl_->frameIndex_->GetRawFrame(target, frameId);
-
-    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
-    switch (transferSyntax)
-    {
-      case EXS_JPEGProcess1:
-        mime = "image/jpeg";
-        break;
-       
-      case EXS_JPEG2000LosslessOnly:
-      case EXS_JPEG2000:
-        mime = "image/jp2";
-        break;
-
-      default:
-        mime = "application/octet-stream";
-        break;
-    }
-  }
-
-
-  void ParsedDicomFile::InvalidateCache()
-  {
-    pimpl_->frameIndex_.reset(NULL);
-  }
-
-
-  unsigned int ParsedDicomFile::GetFramesCount() const
-  {
-    return DicomFrameIndex::GetFramesCount(*pimpl_->file_);
-  }
-
-
-  void ParsedDicomFile::ChangeEncoding(Encoding target)
-  {
-    Encoding source = GetEncoding();
-
-    if (source != target)  // Avoid unnecessary conversion
-    {
-      ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target));
-      FromDcmtkBridge::ChangeStringEncoding(*pimpl_->file_->getDataset(), source, target);
-    }
-  }
-
-
-  void ParsedDicomFile::ExtractDicomSummary(DicomMap& target) const
-  {
-    FromDcmtkBridge::ExtractDicomSummary(target, *pimpl_->file_->getDataset());
-  }
-
-
-  void ParsedDicomFile::ExtractDicomAsJson(Json::Value& target) const
-  {
-    FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset());
-  }
-
-
-  bool ParsedDicomFile::LookupTransferSyntax(std::string& result)
-  {
-    return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_);
-  }
-
-
-  bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const
-  {
-    DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(),
-                DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement());
-
-    DcmDataset& dataset = *pimpl_->file_->getDataset();
-
-    const char *c = NULL;
-    if (dataset.findAndGetString(k, c).good() &&
-        c != NULL)
-    {
-      result = StringToPhotometricInterpretation(c);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-}
--- a/OrthancServer/ParsedDicomFile.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,204 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * 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/Images/ImageAccessor.h"
-#include "../Core/IDynamicObject.h"
-#include "../Core/RestApi/RestApiOutput.h"
-#include "../Core/Toolbox.h"
-#include "ServerEnumerations.h"
-
-#if !defined(ORTHANC_ENABLE_JPEG)
-#  error Macro ORTHANC_ENABLE_JPEG must be defined to use this file
-#endif
-
-#if !defined(ORTHANC_ENABLE_PNG)
-#  error Macro ORTHANC_ENABLE_PNG must be defined to use this file
-#endif
-
-class DcmDataset;
-class DcmFileFormat;
-
-namespace Orthanc
-{
-  class ParsedDicomFile : public IDynamicObject
-  {
-  private:
-    struct PImpl;
-    PImpl* pimpl_;
-
-    ParsedDicomFile(ParsedDicomFile& other);
-
-    void CreateFromDicomMap(const DicomMap& source,
-                            Encoding defaultEncoding);
-
-    void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep);
-
-    void UpdateStorageUid(const DicomTag& tag,
-                          const std::string& value,
-                          bool decodeDataUriScheme);
-
-    void InvalidateCache();
-
-    bool EmbedContentInternal(const std::string& dataUriScheme);
-
-  public:
-    ParsedDicomFile(bool createIdentifiers);  // Create a minimal DICOM instance
-
-    ParsedDicomFile(const DicomMap& map,
-                    Encoding defaultEncoding);
-
-    ParsedDicomFile(const DicomMap& map);
-
-    ParsedDicomFile(const void* content,
-                    size_t size);
-
-    ParsedDicomFile(const std::string& content);
-
-    ParsedDicomFile(DcmDataset& dicom);
-
-    ParsedDicomFile(DcmFileFormat& dicom);
-
-    ~ParsedDicomFile();
-
-    DcmFileFormat& GetDcmtkObject() const;
-
-    ParsedDicomFile* Clone();
-
-    void SendPathValue(RestApiOutput& output,
-                       const UriComponents& uri);
-
-    void Answer(RestApiOutput& output);
-
-    void Remove(const DicomTag& tag);
-
-    // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization)
-    void Clear(const DicomTag& tag,
-               bool onlyIfExists);
-
-    void Replace(const DicomTag& tag,
-                 const std::string& utf8Value,
-                 bool decodeDataUriScheme,
-                 DicomReplaceMode mode);
-
-    void Replace(const DicomTag& tag,
-                 const Json::Value& value,  // Assumed to be encoded with UTF-8
-                 bool decodeDataUriScheme,
-                 DicomReplaceMode mode);
-
-    void Insert(const DicomTag& tag,
-                const Json::Value& value,   // Assumed to be encoded with UTF-8
-                bool decodeDataUriScheme);
-
-    void ReplacePlainString(const DicomTag& tag,
-                            const std::string& utf8Value)
-    {
-      Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent);
-    }
-
-    void RemovePrivateTags()
-    {
-      RemovePrivateTagsInternal(NULL);
-    }
-
-    void RemovePrivateTags(const std::set<DicomTag>& toKeep)
-    {
-      RemovePrivateTagsInternal(&toKeep);
-    }
-
-    // WARNING: This function handles the decoding of strings to UTF8
-    bool GetTagValue(std::string& value,
-                     const DicomTag& tag);
-
-    DicomInstanceHasher GetHasher();
-
-    void SaveToMemoryBuffer(std::string& buffer);
-
-    void SaveToFile(const std::string& path);
-
-    void EmbedContent(const std::string& dataUriScheme);
-
-    void EmbedImage(const ImageAccessor& accessor);
-
-#if (ORTHANC_ENABLE_JPEG == 1 &&  \
-     ORTHANC_ENABLE_PNG == 1)
-    void EmbedImage(const std::string& mime,
-                    const std::string& content);
-#endif
-
-    Encoding GetEncoding() const;
-
-    // WARNING: This function only sets the encoding, it will not
-    // convert the encoding of the tags. Use "ChangeEncoding()" if need be.
-    void SetEncoding(Encoding encoding);
-
-    void DatasetToJson(Json::Value& target, 
-                       DicomToJsonFormat format,
-                       DicomToJsonFlags flags,
-                       unsigned int maxStringLength);
-
-    // This version uses the default parameters for
-    // FileContentType_DicomAsJson
-    void DatasetToJson(Json::Value& target);
-
-    void HeaderToJson(Json::Value& target, 
-                      DicomToJsonFormat format);
-
-    bool HasTag(const DicomTag& tag) const;
-
-    void EmbedPdf(const std::string& pdf);
-
-    bool ExtractPdf(std::string& pdf);
-
-    void GetRawFrame(std::string& target, // OUT
-                     std::string& mime,   // OUT
-                     unsigned int frameId);  // IN
-
-    unsigned int GetFramesCount() const;
-
-    static ParsedDicomFile* CreateFromJson(const Json::Value& value,
-                                           DicomFromJsonFlags flags);
-
-    void ChangeEncoding(Encoding target);
-
-    void ExtractDicomSummary(DicomMap& target) const;
-
-    void ExtractDicomAsJson(Json::Value& target) const;
-
-    bool LookupTransferSyntax(std::string& result);
-
-    bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const;
-  };
-}
--- a/OrthancServer/PrecompiledHeadersServer.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/PrecompiledHeadersServer.h	Tue Aug 29 21:17:35 2017 +0200
@@ -37,42 +37,5 @@
 
 #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/QueryRetrieveHandler.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/QueryRetrieveHandler.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -35,7 +35,7 @@
 #include "QueryRetrieveHandler.h"
 
 #include "OrthancInitialization.h"
-#include "FromDcmtkBridge.h"
+#include "../Core/DicomParsing/FromDcmtkBridge.h"
 
 
 namespace Orthanc
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/Scheduler/ModifyInstanceCommand.h	Tue Aug 29 21:17:35 2017 +0200
@@ -35,7 +35,7 @@
 
 #include "IServerCommand.h"
 #include "../ServerContext.h"
-#include "../DicomModification.h"
+#include "../../Core/DicomParsing/DicomModification.h"
 
 namespace Orthanc
 {
--- a/OrthancServer/Search/HierarchicalMatcher.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/Search/HierarchicalMatcher.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -36,8 +36,8 @@
 
 #include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
-#include "../FromDcmtkBridge.h"
-#include "../ToDcmtkBridge.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/ToDcmtkBridge.h"
 #include "../OrthancInitialization.h"
 
 #include <dcmtk/dcmdata/dcfilefo.h>
--- a/OrthancServer/Search/HierarchicalMatcher.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/Search/HierarchicalMatcher.h	Tue Aug 29 21:17:35 2017 +0200
@@ -33,9 +33,8 @@
 
 #pragma once
 
-#include "../../Core/DicomFormat/DicomMap.h"
 #include "IFindConstraint.h"
-#include "../ParsedDicomFile.h"
+#include "../../Core/DicomParsing/ParsedDicomFile.h"
 
 class DcmItem;
 
--- a/OrthancServer/Search/IFindConstraint.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/Search/IFindConstraint.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -39,7 +39,7 @@
 #include "ValueConstraint.h"
 #include "WildcardConstraint.h"
 
-#include "../FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../../Core/OrthancException.h"
 
 namespace Orthanc
--- a/OrthancServer/Search/LookupIdentifierQuery.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/Search/LookupIdentifierQuery.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -36,7 +36,7 @@
 
 #include "../../Core/OrthancException.h"
 #include "SetOfResources.h"
-#include "../FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
 
 #include <cassert>
 
--- a/OrthancServer/Search/LookupResource.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/Search/LookupResource.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -37,7 +37,7 @@
 #include "../../Core/OrthancException.h"
 #include "../../Core/FileStorage/StorageAccessor.h"
 #include "../ServerToolbox.h"
-#include "../FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
 
 
 namespace Orthanc
--- a/OrthancServer/ServerContext.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/ServerContext.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -38,7 +38,7 @@
 #include "../Core/HttpServer/FilesystemHttpSender.h"
 #include "../Core/HttpServer/HttpStreamTranscoder.h"
 #include "../Core/Logging.h"
-#include "FromDcmtkBridge.h"
+#include "../Core/DicomParsing/FromDcmtkBridge.h"
 #include "ServerToolbox.h"
 #include "OrthancInitialization.h"
 
--- a/OrthancServer/ServerContext.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/ServerContext.h	Tue Aug 29 21:17:35 2017 +0200
@@ -41,10 +41,10 @@
 #include "../Core/RestApi/RestApiOutput.h"
 #include "../Plugins/Engine/OrthancPlugins.h"
 #include "DicomInstanceToStore.h"
-#include "DicomProtocol/ReusableDicomUserConnection.h"
+#include "../Core/DicomNetworking/ReusableDicomUserConnection.h"
 #include "IServerListener.h"
 #include "LuaScripting.h"
-#include "ParsedDicomFile.h"
+#include "../Core/DicomParsing/ParsedDicomFile.h"
 #include "Scheduler/ServerScheduler.h"
 #include "ServerIndex.h"
 #include "OrthancHttpHandler.h"
--- a/OrthancServer/ServerEnumerations.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -317,197 +317,6 @@
     }
   }
 
-
-  const char* EnumerationToString(ModalityManufacturer manufacturer)
-  {
-    switch (manufacturer)
-    {
-      case ModalityManufacturer_Generic:
-        return "Generic";
-
-      case ModalityManufacturer_GenericNoWildcardInDates:
-        return "GenericNoWildcardInDates";
-
-      case ModalityManufacturer_GenericNoUniversalWildcard:
-        return "GenericNoUniversalWildcard";
-
-      case ModalityManufacturer_StoreScp:
-        return "StoreScp";
-      
-      case ModalityManufacturer_ClearCanvas:
-        return "ClearCanvas";
-      
-      case ModalityManufacturer_Dcm4Chee:
-        return "Dcm4Chee";
-      
-      case ModalityManufacturer_Vitrea:
-        return "Vitrea";
-      
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(DicomRequestType type)
-  {
-    switch (type)
-    {
-      case DicomRequestType_Echo:
-        return "Echo";
-        break;
-
-      case DicomRequestType_Find:
-        return "Find";
-        break;
-
-      case DicomRequestType_Get:
-        return "Get";
-        break;
-
-      case DicomRequestType_Move:
-        return "Move";
-        break;
-
-      case DicomRequestType_Store:
-        return "Store";
-        break;
-
-      default: 
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-
-  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer)
-  {
-    ModalityManufacturer result;
-    bool obsolete = false;
-    
-    if (manufacturer == "Generic")
-    {
-      return ModalityManufacturer_Generic;
-    }
-    else if (manufacturer == "GenericNoWildcardInDates")
-    {
-      return ModalityManufacturer_GenericNoWildcardInDates;
-    }
-    else if (manufacturer == "GenericNoUniversalWildcard")
-    {
-      return ModalityManufacturer_GenericNoUniversalWildcard;
-    }
-    else if (manufacturer == "ClearCanvas")
-    {
-      return ModalityManufacturer_ClearCanvas;
-    }
-    else if (manufacturer == "StoreScp")
-    {
-      return ModalityManufacturer_StoreScp;
-    }
-    else if (manufacturer == "Dcm4Chee")
-    {
-      return ModalityManufacturer_Dcm4Chee;
-    }
-    else if (manufacturer == "Vitrea")
-    {
-      return ModalityManufacturer_Vitrea;
-    }
-    else if (manufacturer == "AgfaImpax" ||
-             manufacturer == "SyngoVia")
-    {
-      result = ModalityManufacturer_GenericNoWildcardInDates;
-      obsolete = true;
-    }
-    else if (manufacturer == "EFilm2" ||
-             manufacturer == "MedInria")
-    {
-      result = ModalityManufacturer_Generic;
-      obsolete = true;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    if (obsolete)
-    {
-      LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is obsolete since "
-                   << "Orthanc 1.3.0. To guarantee compatibility with future Orthanc "
-                   << "releases, you should replace it by \""
-                   << EnumerationToString(result)
-                   << "\" in your configuration file.";
-    }
-
-    return result;
-  }
-
-
-  const char* EnumerationToString(TransferSyntax syntax)
-  {
-    switch (syntax)
-    {
-      case TransferSyntax_Deflated:
-        return "Deflated";
-
-      case TransferSyntax_Jpeg:
-        return "JPEG";
-
-      case TransferSyntax_Jpeg2000:
-        return "JPEG2000";
-
-      case TransferSyntax_JpegLossless:
-        return "JPEG Lossless";
-
-      case TransferSyntax_Jpip:
-        return "JPIP";
-
-      case TransferSyntax_Mpeg2:
-        return "MPEG2";
-
-      case TransferSyntax_Rle:
-        return "RLE";
-
-      default: 
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  const char* EnumerationToString(DicomVersion version)
-  {
-    switch (version)
-    {
-      case DicomVersion_2008:
-        return "2008";
-        break;
-
-      case DicomVersion_2017c:
-        return "2017c";
-        break;
-
-      default: 
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  DicomVersion StringToDicomVersion(const std::string& version)
-  {
-    if (version == "2008")
-    {
-      return DicomVersion_2008;
-    }
-    else if (version == "2017c")
-    {
-      return DicomVersion_2017c;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
   
   bool IsUserMetadata(MetadataType metadata)
   {
--- a/OrthancServer/ServerEnumerations.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/ServerEnumerations.h	Tue Aug 29 21:17:35 2017 +0200
@@ -56,75 +56,6 @@
     StoreStatus_FilteredOut     // Removed by NewInstanceFilter
   };
 
-  enum ModalityManufacturer
-  {
-    ModalityManufacturer_Generic,
-    ModalityManufacturer_GenericNoWildcardInDates,
-    ModalityManufacturer_GenericNoUniversalWildcard,
-    ModalityManufacturer_StoreScp,
-    ModalityManufacturer_ClearCanvas,
-    ModalityManufacturer_Dcm4Chee,
-    ModalityManufacturer_Vitrea
-  };
-
-  enum DicomRequestType
-  {
-    DicomRequestType_Echo,
-    DicomRequestType_Find,
-    DicomRequestType_Get,
-    DicomRequestType_Move,
-    DicomRequestType_Store
-  };
-
-  enum DicomReplaceMode
-  {
-    DicomReplaceMode_InsertIfAbsent,
-    DicomReplaceMode_ThrowIfAbsent,
-    DicomReplaceMode_IgnoreIfAbsent
-  };
-
-  enum TransferSyntax
-  {
-    TransferSyntax_Deflated,
-    TransferSyntax_Jpeg,
-    TransferSyntax_Jpeg2000,
-    TransferSyntax_JpegLossless,
-    TransferSyntax_Jpip,
-    TransferSyntax_Mpeg2,
-    TransferSyntax_Rle
-  };
-
-  enum DicomToJsonFormat
-  {
-    DicomToJsonFormat_Full,
-    DicomToJsonFormat_Short,
-    DicomToJsonFormat_Human
-  };
-
-  enum DicomToJsonFlags
-  {
-    DicomToJsonFlags_IncludeBinary         = (1 << 0),
-    DicomToJsonFlags_IncludePrivateTags    = (1 << 1),
-    DicomToJsonFlags_IncludeUnknownTags    = (1 << 2),
-    DicomToJsonFlags_IncludePixelData      = (1 << 3),
-    DicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),
-    DicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),
-
-    // Some predefined combinations
-    DicomToJsonFlags_None     = 0,
-    DicomToJsonFlags_Default  = (DicomToJsonFlags_IncludeBinary |
-                                 DicomToJsonFlags_IncludePixelData | 
-                                 DicomToJsonFlags_IncludePrivateTags | 
-                                 DicomToJsonFlags_IncludeUnknownTags | 
-                                 DicomToJsonFlags_ConvertBinaryToNull)
-  };
-
-  enum DicomFromJsonFlags
-  {
-    DicomFromJsonFlags_DecodeDataUriScheme = (1 << 0),
-    DicomFromJsonFlags_GenerateIdentifiers = (1 << 1)
-  };
-
   enum IdentifierConstraintType
   {
     IdentifierConstraintType_Equal,
@@ -133,12 +64,6 @@
     IdentifierConstraintType_Wildcard        /* Case sensitive, "*" or "?" are the only allowed wildcards */
   };
 
-  enum DicomVersion
-  {
-    DicomVersion_2008,
-    DicomVersion_2017c
-  };
-
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
@@ -228,17 +153,5 @@
 
   const char* EnumerationToString(ChangeType type);
 
-  const char* EnumerationToString(ModalityManufacturer manufacturer);
-
-  const char* EnumerationToString(DicomRequestType type);
-
-  const char* EnumerationToString(TransferSyntax syntax);
-
-  const char* EnumerationToString(DicomVersion version);
-
-  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer);
-
-  DicomVersion StringToDicomVersion(const std::string& version);
-
   bool IsUserMetadata(MetadataType type);
 }
--- a/OrthancServer/ServerIndex.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/ServerIndex.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -41,13 +41,13 @@
 #include "ServerIndexChange.h"
 #include "EmbeddedResources.h"
 #include "OrthancInitialization.h"
-#include "ParsedDicomFile.h"
+#include "../Core/DicomParsing/ParsedDicomFile.h"
 #include "ServerToolbox.h"
 #include "../Core/Toolbox.h"
 #include "../Core/Logging.h"
 #include "../Core/DicomFormat/DicomArray.h"
 
-#include "FromDcmtkBridge.h"
+#include "../Core/DicomParsing/FromDcmtkBridge.h"
 #include "ServerContext.h"
 #include "DicomInstanceToStore.h"
 #include "Search/LookupResource.h"
--- a/OrthancServer/ToDcmtkBridge.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,150 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "PrecompiledHeadersServer.h"
-#include "ToDcmtkBridge.h"
-
-#include <memory>
-#include <dcmtk/dcmnet/diutil.h>
-
-#include "../Core/OrthancException.h"
-
-
-namespace Orthanc
-{
-  DcmEVR ToDcmtkBridge::Convert(ValueRepresentation vr)
-  {
-    switch (vr)
-    {
-      case ValueRepresentation_ApplicationEntity:
-        return EVR_AE;
-
-      case ValueRepresentation_AgeString:
-        return EVR_AS;
-
-      case ValueRepresentation_AttributeTag:
-        return EVR_AT;
-
-      case ValueRepresentation_CodeString:
-        return EVR_CS;
-
-      case ValueRepresentation_Date:
-        return EVR_DA;
-
-      case ValueRepresentation_DecimalString:
-        return EVR_DS;
-
-      case ValueRepresentation_DateTime:
-        return EVR_DT;
-
-      case ValueRepresentation_FloatingPointSingle:
-        return EVR_FL;
-
-      case ValueRepresentation_FloatingPointDouble:
-        return EVR_FD;
-
-      case ValueRepresentation_IntegerString:
-        return EVR_IS;
-
-      case ValueRepresentation_LongString:
-        return EVR_LO;
-
-      case ValueRepresentation_LongText:
-        return EVR_LT;
-
-      case ValueRepresentation_OtherByte:
-        return EVR_OB;
-
-        // Not supported as of DCMTK 3.6.0
-        /*case ValueRepresentation_OtherDouble:
-          return EVR_OD;*/
-
-      case ValueRepresentation_OtherFloat:
-        return EVR_OF;
-
-        // Not supported as of DCMTK 3.6.0
-        /*case ValueRepresentation_OtherLong:
-          return EVR_OL;*/
-
-      case ValueRepresentation_OtherWord:
-        return EVR_OW;
-
-      case ValueRepresentation_PersonName:
-        return EVR_PN;
-
-      case ValueRepresentation_ShortString:
-        return EVR_SH;
-
-      case ValueRepresentation_SignedLong:
-        return EVR_SL;
-
-      case ValueRepresentation_Sequence:
-        return EVR_SQ;
-
-      case ValueRepresentation_SignedShort:
-        return EVR_SS;
-
-      case ValueRepresentation_ShortText:
-        return EVR_ST;
-
-      case ValueRepresentation_Time:
-        return EVR_TM;
-
-        // Not supported as of DCMTK 3.6.0
-        /*case ValueRepresentation_UnlimitedCharacters:
-          return EVR_UC;*/
-
-      case ValueRepresentation_UniqueIdentifier:
-        return EVR_UI;
-
-      case ValueRepresentation_UnsignedLong:
-        return EVR_UL;
-
-      case ValueRepresentation_Unknown:
-        return EVR_UN;
-
-        // Not supported as of DCMTK 3.6.0
-        /*case ValueRepresentation_UniversalResource:
-          return EVR_UR;*/
-
-      case ValueRepresentation_UnsignedShort:
-        return EVR_US;
-
-      case ValueRepresentation_UnlimitedText:
-        return EVR_UT;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-}
--- a/OrthancServer/ToDcmtkBridge.h	Tue Aug 29 19:59:01 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017 Osimis, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * In addition, as a special exception, the copyright holders of this
- * program give permission to link the code of its release with the
- * OpenSSL project's "OpenSSL" library (or with modified versions of it
- * that use the same license as the "OpenSSL" library), and distribute
- * the linked executables. You must obey the GNU General Public License
- * in all respects for all of the code used other than "OpenSSL". If you
- * modify file(s) with this exception, you may extend this exception to
- * your version of the file(s), but you are not obligated to do so. If
- * you do not wish to do so, delete this exception statement from your
- * version. If you delete this exception statement from all source files
- * in the program, then also delete it here.
- * 
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if ORTHANC_ENABLE_DCMTK != 1
-#  error The macro ORTHANC_ENABLE_DCMTK must be set to 1
-#endif
-
-#include "../Core/DicomFormat/DicomMap.h"
-#include <dcmtk/dcmdata/dcdatset.h>
-
-namespace Orthanc
-{
-  class ToDcmtkBridge
-  {
-  public:
-    static DcmTagKey Convert(const DicomTag& tag)
-    {
-      return DcmTagKey(tag.GetGroup(), tag.GetElement());
-    }
-
-    static DcmEVR Convert(ValueRepresentation vr);
-  };
-}
--- a/OrthancServer/main.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/OrthancServer/main.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -41,15 +41,15 @@
 #include "../Core/HttpServer/FilesystemHttpHandler.h"
 #include "../Core/Lua/LuaFunctionCall.h"
 #include "../Core/DicomFormat/DicomArray.h"
-#include "DicomProtocol/DicomServer.h"
-#include "DicomProtocol/ReusableDicomUserConnection.h"
+#include "../Core/DicomNetworking/DicomServer.h"
+#include "../Core/DicomNetworking/ReusableDicomUserConnection.h"
 #include "OrthancInitialization.h"
 #include "ServerContext.h"
 #include "OrthancFindRequestHandler.h"
 #include "OrthancMoveRequestHandler.h"
 #include "ServerToolbox.h"
 #include "../Plugins/Engine/OrthancPlugins.h"
-#include "FromDcmtkBridge.h"
+#include "../Core/DicomParsing/FromDcmtkBridge.h"
 
 using namespace Orthanc;
 
--- a/Plugins/Engine/OrthancPlugins.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -45,13 +45,13 @@
 #include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
 #include "../../Core/Toolbox.h"
-#include "../../OrthancServer/FromDcmtkBridge.h"
-#include "../../OrthancServer/ToDcmtkBridge.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/ToDcmtkBridge.h"
 #include "../../OrthancServer/OrthancInitialization.h"
 #include "../../OrthancServer/ServerContext.h"
 #include "../../OrthancServer/ServerToolbox.h"
 #include "../../OrthancServer/Search/HierarchicalMatcher.h"
-#include "../../OrthancServer/Internals/DicomImageDecoder.h"
+#include "../../Core/DicomParsing/Internals/DicomImageDecoder.h"
 #include "../../Core/Compression/ZlibCompressor.h"
 #include "../../Core/Compression/GzipCompressor.h"
 #include "../../Core/Images/Image.h"
--- a/Plugins/Engine/OrthancPlugins.h	Tue Aug 29 19:59:01 2017 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Tue Aug 29 21:17:35 2017 +0200
@@ -53,14 +53,14 @@
 
 #else
 
+#include "../../Core/DicomNetworking/IFindRequestHandlerFactory.h"
+#include "../../Core/DicomNetworking/IMoveRequestHandlerFactory.h"
+#include "../../Core/DicomNetworking/IWorklistRequestHandlerFactory.h"
 #include "../../Core/FileStorage/IStorageArea.h"
 #include "../../Core/HttpServer/IHttpHandler.h"
 #include "../../Core/HttpServer/IIncomingHttpRequestFilter.h"
+#include "../../OrthancServer/IDicomImageDecoder.h"
 #include "../../OrthancServer/IServerListener.h"
-#include "../../OrthancServer/IDicomImageDecoder.h"
-#include "../../OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h"
-#include "../../OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h"
-#include "../../OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h"
 #include "OrthancPluginDatabase.h"
 #include "PluginsManager.h"
 
--- a/UnitTestsSources/DicomMapTests.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/UnitTestsSources/DicomMapTests.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -36,7 +36,7 @@
 
 #include "../Core/OrthancException.h"
 #include "../Core/DicomFormat/DicomMap.h"
-#include "../OrthancServer/FromDcmtkBridge.h"
+#include "../Core/DicomParsing/FromDcmtkBridge.h"
 
 #include <memory>
 
--- a/UnitTestsSources/FromDcmtkTests.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -34,9 +34,9 @@
 #include "PrecompiledHeadersUnitTests.h"
 #include "gtest/gtest.h"
 
-#include "../OrthancServer/FromDcmtkBridge.h"
-#include "../OrthancServer/ToDcmtkBridge.h"
-#include "../OrthancServer/DicomModification.h"
+#include "../Core/DicomParsing/FromDcmtkBridge.h"
+#include "../Core/DicomParsing/ToDcmtkBridge.h"
+#include "../Core/DicomParsing/DicomModification.h"
 #include "../OrthancServer/ServerToolbox.h"
 #include "../Core/OrthancException.h"
 #include "../Core/Images/ImageBuffer.h"
@@ -46,8 +46,8 @@
 #include "../Core/Images/ImageProcessing.h"
 #include "../Core/Endianness.h"
 #include "../Resources/EncodingTests.h"
-#include "../OrthancServer/DicomProtocol/DicomFindAnswers.h"
-#include "../OrthancServer/Internals/DicomImageDecoder.h"
+#include "../Core/DicomNetworking/DicomFindAnswers.h"
+#include "../Core/DicomParsing/Internals/DicomImageDecoder.h"
 #include "../Plugins/Engine/PluginsEnumerations.h"
 
 #include <dcmtk/dcmdata/dcelem.h>
--- a/UnitTestsSources/JpegLosslessTests.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/UnitTestsSources/JpegLosslessTests.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -34,13 +34,13 @@
 #include "PrecompiledHeadersUnitTests.h"
 #include "gtest/gtest.h"
 
-#include "../OrthancServer/Internals/DicomImageDecoder.h"
+#include "../Core/DicomParsing/Internals/DicomImageDecoder.h"
 
 #if ORTHANC_ENABLE_JPEG_LOSSLESS == 1
 
 #include <dcmtk/dcmdata/dcfilefo.h>
 
-#include "../OrthancServer/ParsedDicomFile.h"
+#include "../Core/DicomParsing/ParsedDicomFile.h"
 #include "../Core/OrthancException.h"
 #include "../Core/Images/ImageBuffer.h"
 #include "../Core/Images/PngWriter.h"
--- a/UnitTestsSources/MultiThreadingTests.cpp	Tue Aug 29 19:59:01 2017 +0200
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -129,7 +129,7 @@
 
 
 
-#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h"
+#include "../Core/DicomNetworking/ReusableDicomUserConnection.h"
 
 TEST(ReusableDicomUserConnection, DISABLED_Basic)
 {