changeset 1151:48befc2bf66d broker

ParseDicomFromWadoCommand
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 15 Nov 2019 17:34:19 +0100
parents 7aad0984d38a
children 78b8bfe154bc
files Framework/Oracle/GenericOracleRunner.cpp Framework/Oracle/IOracleCommand.h Framework/Oracle/ParseDicomFileCommand.cpp Framework/Oracle/ParseDicomFileCommand.h Framework/Oracle/ParseDicomFromFileCommand.cpp Framework/Oracle/ParseDicomFromFileCommand.h Framework/Oracle/ParseDicomFromWadoCommand.cpp Framework/Oracle/ParseDicomFromWadoCommand.h Framework/Oracle/ParseDicomSuccessMessage.cpp Framework/Oracle/ParseDicomSuccessMessage.h Framework/Toolbox/ParsedDicomCache.cpp Framework/Toolbox/ParsedDicomCache.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 13 files changed, 656 insertions(+), 393 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Oracle/GenericOracleRunner.cpp	Fri Nov 15 12:38:15 2019 +0100
+++ b/Framework/Oracle/GenericOracleRunner.cpp	Fri Nov 15 17:34:19 2019 +0100
@@ -30,11 +30,15 @@
 #include "HttpCommand.h"
 #include "OracleCommandExceptionMessage.h"
 #include "OrthancRestApiCommand.h"
+#include "ParseDicomFromFileCommand.h"
+#include "ParseDicomFromWadoCommand.h"
 #include "ReadFileCommand.h"
 
 #if ORTHANC_ENABLE_DCMTK == 1
-#  include "ParseDicomFileCommand.h"
+#  include "ParseDicomSuccessMessage.h"
 #  include <dcmtk/dcmdata/dcdeftag.h>
+static unsigned int BUCKET_DICOMDIR = 0;
+static unsigned int BUCKET_SOP = 1;
 #endif
 
 #include <Core/Compression/GzipCompressor.h>
@@ -45,6 +49,8 @@
 
 #include <boost/filesystem.hpp>
 
+#include <dcmtk/dcmdata/dcfilefo.h>
+
 
 namespace OrthancStone
 {
@@ -100,9 +106,9 @@
   }
 
 
-  static void RunInternal(boost::weak_ptr<IObserver> receiver,
-                          IMessageEmitter& emitter,
-                          const HttpCommand& command)
+  static void RunHttpCommand(std::string& answer,
+                             Orthanc::HttpClient::HttpHeaders& answerHeaders,
+                             const HttpCommand& command)
   {
     Orthanc::HttpClient client;
     client.SetUrl(command.GetUrl());
@@ -122,21 +128,28 @@
       client.SetBody(command.GetBody());
     }
 
-    std::string answer;
-    Orthanc::HttpClient::HttpHeaders answerHeaders;
     client.ApplyAndThrowException(answer, answerHeaders);
-
     DecodeAnswer(answer, answerHeaders);
-
-    HttpCommand::SuccessMessage message(command, answerHeaders, answer);
-    emitter.EmitMessage(receiver, message);
   }
 
 
   static void RunInternal(boost::weak_ptr<IObserver> receiver,
                           IMessageEmitter& emitter,
-                          const Orthanc::WebServiceParameters& orthanc,
-                          const OrthancRestApiCommand& command)
+                          const HttpCommand& command)
+  {
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    RunHttpCommand(answer, answerHeaders, command);
+    
+    HttpCommand::SuccessMessage message(command, answerHeaders, answer);
+    emitter.EmitMessage(receiver, message);
+  }
+
+  
+  static void RunOrthancRestApiCommand(std::string& answer,
+                                       Orthanc::HttpClient::HttpHeaders& answerHeaders,
+                                       const Orthanc::WebServiceParameters& orthanc,
+                                       const OrthancRestApiCommand& command)
   {
     Orthanc::HttpClient client(orthanc, command.GetUri());
     client.SetMethod(command.GetMethod());
@@ -150,11 +163,19 @@
       client.SetBody(command.GetBody());
     }
 
+    client.ApplyAndThrowException(answer, answerHeaders);
+    DecodeAnswer(answer, answerHeaders);
+  }
+
+  
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const OrthancRestApiCommand& command)
+  {
     std::string answer;
     Orthanc::HttpClient::HttpHeaders answerHeaders;
-    client.ApplyAndThrowException(answer, answerHeaders);
-
-    DecodeAnswer(answer, answerHeaders);
+    RunOrthancRestApiCommand(answer, answerHeaders, orthanc, command);
 
     OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
     emitter.EmitMessage(receiver, message);
@@ -238,160 +259,70 @@
 
 
 #if ORTHANC_ENABLE_DCMTK == 1
-  namespace
+  static Orthanc::ParsedDicomFile* ParseDicom(uint64_t& fileSize,  /* OUT */
+                                              const std::string& path,
+                                              bool isPixelData)
   {
-    class IDicomHandler : public boost::noncopyable
+    if (!Orthanc::SystemToolbox::IsRegularFile(path))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile);
+    }
+    
+    LOG(TRACE) << "Parsing DICOM file, " << (isPixelData ? "with" : "without")
+               << " pixel data: " << path;
+    
+    boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
+    
+    fileSize = Orthanc::SystemToolbox::GetFileSize(path);
+    
+    // Check for 32bit systems
+    if (fileSize != static_cast<uint64_t>(static_cast<size_t>(fileSize)))
     {
-    public:
-      virtual ~IDicomHandler()
-      {
-      }
-
-      virtual void Handle(Orthanc::ParsedDicomFile* dicom,
-                          const ParseDicomFileCommand& command,
-                          const std::string& path,
-                          uint64_t fileSize) = 0;
-
-      static void Apply(IDicomHandler& handler,
-                        const std::string& path,
-                        const ParseDicomFileCommand& command)
-      {
-        if (!Orthanc::SystemToolbox::IsRegularFile(path))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile);
-        }
-
-        LOG(TRACE) << "Parsing DICOM file, " 
-                   << (command.IsPixelDataIncluded() ? "with" : "without")
-                   << " pixel data: " << path;
-
-        boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
-
-        uint64_t fileSize = Orthanc::SystemToolbox::GetFileSize(path);
-
-        // Check for 32bit systems
-        if (fileSize != static_cast<uint64_t>(static_cast<size_t>(fileSize)))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
-        }
-
-        DcmFileFormat dicom;
-        bool ok;
-
-        if (command.IsPixelDataIncluded())
-        {
-          ok = dicom.loadFile(path.c_str()).good();
-        }
-        else
-        {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+    
+    DcmFileFormat dicom;
+    bool ok;
+    
+    if (isPixelData)
+    {
+      ok = dicom.loadFile(path.c_str()).good();
+    }
+    else
+    {
 #if DCMTK_VERSION_NUMBER >= 362
-          /**
-           * NB : We could stop at (0x3007, 0x0000) instead of
-           * DCM_PixelData as the Stone framework does not use further
-           * tags (cf. the Orthanc::DICOM_TAG_* constants), but we
-           * still use "PixelData" as this does not change the runtime
-           * much, and as it is more explicit.
-           **/
-          static const DcmTagKey STOP = DCM_PixelData;
-          //static const DcmTagKey STOP(0x3007, 0x0000);
-
-          ok = dicom.loadFileUntilTag(path.c_str(), EXS_Unknown, EGL_noChange,
-                                      DCM_MaxReadLength, ERM_autoDetect, STOP).good();
-#else
-          // The primitive "loadFileUntilTag" was introduced in DCMTK 3.6.2
-          ok = dicom.loadFile(path.c_str()).good();
-#endif
-        }
-
-        if (ok)
-        {
-          handler.Handle(new Orthanc::ParsedDicomFile(dicom), command, path, fileSize);
+      /**
+       * NB : We could stop at (0x3007, 0x0000) instead of
+       * DCM_PixelData as the Stone framework does not use further
+       * tags (cf. the Orthanc::DICOM_TAG_* constants), but we still
+       * use "PixelData" as this does not change the runtime much, and
+       * as it is more explicit.
+       **/
+      static const DcmTagKey STOP = DCM_PixelData;
+      //static const DcmTagKey STOP(0x3007, 0x0000);
 
-          boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
-          LOG(TRACE) << path << ": parsed in " << (end-start).total_milliseconds() << " ms";
-        }
-        else
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                          "Cannot parse file: " + path);
-        }
-      }
-    };
-
+      ok = dicom.loadFileUntilTag(path.c_str(), EXS_Unknown, EGL_noChange,
+                                  DCM_MaxReadLength, ERM_autoDetect, STOP).good();
+#else
+      // The primitive "loadFileUntilTag" was introduced in DCMTK 3.6.2
+      ok = dicom.loadFile(path.c_str()).good();
+#endif
+    }
 
-    class DicomHandlerWithoutCache : public IDicomHandler
-    {
-    private:
-      boost::weak_ptr<IObserver> receiver_;
-      IMessageEmitter&           emitter_;
-      
-    public:
-      DicomHandlerWithoutCache(boost::weak_ptr<IObserver> receiver,
-                               IMessageEmitter& emitter) :
-        receiver_(receiver),
-        emitter_(emitter)
-      {
-      }        
-      
-      virtual void Handle(Orthanc::ParsedDicomFile* dicom,
-                          const ParseDicomFileCommand& command,
-                          const std::string& path,
-                          uint64_t fileSize)
-      {
-        std::auto_ptr<Orthanc::ParsedDicomFile> parsed(dicom);
-
-        ParseDicomFileCommand::SuccessMessage message
-          (command, *parsed, static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
-        emitter_.EmitMessage(receiver_, message);
-      }
-    };
-
-
-    class DicomHandlerWithCache : public IDicomHandler
+    if (ok)
     {
-    private:
-      boost::weak_ptr<IObserver> receiver_;
-      IMessageEmitter&           emitter_;
-      boost::shared_ptr<ParsedDicomCache>  cache_;
+      std::auto_ptr<Orthanc::ParsedDicomFile> result(new Orthanc::ParsedDicomFile(dicom));
+
+      boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
+      LOG(TRACE) << path << ": parsed in " << (end-start).total_milliseconds() << " ms";
 
-    public:
-      DicomHandlerWithCache(boost::weak_ptr<IObserver> receiver,
-                            IMessageEmitter& emitter,
-                            boost::shared_ptr<ParsedDicomCache> cache) :
-        receiver_(receiver),
-        emitter_(emitter),
-        cache_(cache)
-      {
-        if (!cache)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-        }
-      }
-      
-      virtual void Handle(Orthanc::ParsedDicomFile* dicom,
-                          const ParseDicomFileCommand& command,
-                          const std::string& path,
-                          uint64_t fileSize)
-      {
-        std::auto_ptr<Orthanc::ParsedDicomFile> parsed(dicom);
-
-        {
-          ParseDicomFileCommand::SuccessMessage message
-            (command, *parsed, static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
-          emitter_.EmitMessage(receiver_, message);
-        }
-
-        // Store it into the cache for future use
-        assert(cache_);
-
-        // Invalidate to overwrite DICOM instance that would already
-        // be stored without pixel data
-        cache_->Invalidate(path);
-
-        cache_->Acquire(path, parsed.release(),
-                        static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
-      }
-    };
+      return result.release();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Cannot parse file: " + path);
+    }
   }
 
   
@@ -399,42 +330,105 @@
                           IMessageEmitter& emitter,
                           boost::shared_ptr<ParsedDicomCache> cache,
                           const std::string& root,
-                          const ParseDicomFileCommand& command)
+                          const ParseDicomFromFileCommand& command)
   {
     const std::string path = GetPath(root, command.GetPath());
 
-#if 1
-    if (cache.get())
+    if (cache)
     {
+      ParsedDicomCache::Reader reader(*cache, BUCKET_DICOMDIR, path);
+      if (reader.IsValid() &&
+          (!command.IsPixelDataIncluded() ||
+           reader.HasPixelData()))
       {
-        ParsedDicomCache::Reader reader(*cache, path);
-        if (reader.IsValid() &&
-            (!command.IsPixelDataIncluded() ||
-             reader.HasPixelData()))
-        {
-          // Reuse the DICOM file from the cache
-          ParseDicomFileCommand::SuccessMessage message(
-            command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData());
-          emitter.EmitMessage(receiver, message);
-          return;
-        }
+        // Reuse the DICOM file from the cache
+        ParseDicomSuccessMessage message(command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData());
+        emitter.EmitMessage(receiver, message);
+        return;
       }
+    }
+
+    uint64_t fileSize;
+    std::auto_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicom(fileSize, path, command.IsPixelDataIncluded()));
+
+    if (fileSize != static_cast<size_t>(fileSize))
+    {
+      // Cannot load such a large file on 32-bit architecture
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+    
+    {
+      ParseDicomSuccessMessage message
+        (command, *parsed, static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
+      emitter.EmitMessage(receiver, message);
+    }
+
+    if (cache)
+    {
+      // Store it into the cache for future use
       
-      DicomHandlerWithCache handler(receiver, emitter, cache);
-      IDicomHandler::Apply(handler, path, command);
+      // Invalidate to overwrite DICOM instance that would already
+      // be stored without pixel data
+      cache->Invalidate(BUCKET_DICOMDIR, path);
+      
+      cache->Acquire(BUCKET_DICOMDIR, path, parsed.release(),
+                     static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
     }
-    else
+  }
+
+  
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          boost::shared_ptr<ParsedDicomCache> cache,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const ParseDicomFromWadoCommand& command)
+  {
+    if (cache)
     {
-      // No cache available
-      DicomHandlerWithoutCache handler(receiver, emitter);
-      IDicomHandler::Apply(handler, path, command);
+      ParsedDicomCache::Reader reader(*cache, BUCKET_SOP, command.GetSopInstanceUid());
+      if (reader.IsValid() &&
+          reader.HasPixelData())
+      {
+        // Reuse the DICOM file from the cache
+        ParseDicomSuccessMessage message(command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData());
+        emitter.EmitMessage(receiver, message);
+        return;
+      }
     }
-#else
-    DicomHandlerWithoutCache handler(receiver, emitter);
-    IDicomHandler::Apply(handler, path, command);
-#endif
+
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+
+    switch (command.GetRestCommand().GetType())
+    {
+      case IOracleCommand::Type_Http:
+        RunHttpCommand(answer, answerHeaders, dynamic_cast<const HttpCommand&>(command.GetRestCommand()));
+        break;
+        
+      case IOracleCommand::Type_OrthancRestApi:
+        RunOrthancRestApiCommand(answer, answerHeaders, orthanc,
+                                 dynamic_cast<const OrthancRestApiCommand&>(command.GetRestCommand()));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    size_t fileSize;
+    std::auto_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, answerHeaders));
+
+    {
+      ParseDicomSuccessMessage message(command, *parsed, fileSize,
+                                       true /* pixel data always is included in WADO-RS */);
+      emitter.EmitMessage(receiver, message);
+    }
+
+    if (cache)
+    {
+      // Store it into the cache for future use
+      cache->Acquire(BUCKET_SOP, command.GetSopInstanceUid(), parsed.release(), fileSize, true);
+    }
   }
-  
 #endif
 
 
@@ -476,10 +470,24 @@
                       dynamic_cast<const ReadFileCommand&>(command));
           break;
 
-        case IOracleCommand::Type_ParseDicomFile:
+        case IOracleCommand::Type_ParseDicomFromFile:
+        case IOracleCommand::Type_ParseDicomFromWado:
 #if ORTHANC_ENABLE_DCMTK == 1
-          RunInternal(receiver, emitter, dicomCache_, rootDirectory_,
-                      dynamic_cast<const ParseDicomFileCommand&>(command));
+          switch (command.GetType())
+          {
+            case IOracleCommand::Type_ParseDicomFromFile:
+              RunInternal(receiver, emitter, dicomCache_, rootDirectory_,
+                          dynamic_cast<const ParseDicomFromFileCommand&>(command));
+              break;
+
+            case IOracleCommand::Type_ParseDicomFromWado:
+              RunInternal(receiver, emitter, dicomCache_, orthanc_,
+                          dynamic_cast<const ParseDicomFromWadoCommand&>(command));
+              break;
+
+            default:
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }            
           break;
 #else
           throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
--- a/Framework/Oracle/IOracleCommand.h	Fri Nov 15 12:38:15 2019 +0100
+++ b/Framework/Oracle/IOracleCommand.h	Fri Nov 15 17:34:19 2019 +0100
@@ -34,7 +34,8 @@
       Type_GetOrthancWebViewerJpeg,
       Type_Http,
       Type_OrthancRestApi,
-      Type_ParseDicomFile,
+      Type_ParseDicomFromFile,
+      Type_ParseDicomFromWado,
       Type_ReadFile,
       Type_Sleep
     };
--- a/Framework/Oracle/ParseDicomFileCommand.cpp	Fri Nov 15 12:38:15 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "ParseDicomFileCommand.h"
-
-#include <Core/OrthancException.h>
-
-#include <boost/filesystem/path.hpp>
-
-namespace OrthancStone
-{
-  ParseDicomFileCommand::SuccessMessage::SuccessMessage(const ParseDicomFileCommand& command,
-                                                        Orthanc::ParsedDicomFile& dicom,
-                                                        size_t fileSize,
-                                                        bool hasPixelData) :
-    OriginMessage(command),
-    dicom_(dicom),
-    fileSize_(fileSize),
-    hasPixelData_(hasPixelData)
-  {
-    if (!dicom.GetTagValue(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID))
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
-                                      "DICOM instance missing tag SOPInstanceUID");
-    }
-  }
-
-
-  std::string ParseDicomFileCommand::GetDicomDirPath(const std::string& dicomDirPath,
-                                                     const std::string& file)
-  {
-    std::string tmp = file;
-
-#if !defined(_WIN32)
-    std::replace(tmp.begin(), tmp.end(), '\\', '/');
-#endif
-
-    boost::filesystem::path base = boost::filesystem::path(dicomDirPath).parent_path();
-
-    return (base / tmp).string();
-  }
-}
--- a/Framework/Oracle/ParseDicomFileCommand.h	Fri Nov 15 12:38:15 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if !defined(ORTHANC_ENABLE_DCMTK)
-#  error The macro ORTHANC_ENABLE_DCMTK must be defined
-#endif
-
-#if ORTHANC_ENABLE_DCMTK != 1
-#  error Support for DCMTK must be enabled to use ParseDicomFileCommand
-#endif
-
-#include "../Messages/IMessage.h"
-#include "OracleCommandBase.h"
-
-#include <Core/DicomParsing/ParsedDicomFile.h>
-
-#include <dcmtk/dcmdata/dcfilefo.h>
-
-namespace OrthancStone
-{
-  class ParseDicomFileCommand : public OracleCommandBase
-  {
-  public:
-    class SuccessMessage : public OriginMessage<ParseDicomFileCommand>
-    {
-      ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
-
-    private:
-      Orthanc::ParsedDicomFile&  dicom_;
-      size_t                     fileSize_;
-      bool                       hasPixelData_;
-      std::string                sopInstanceUid_;
-
-    public:
-      SuccessMessage(const ParseDicomFileCommand& command,
-                     Orthanc::ParsedDicomFile& dicom,
-                     size_t fileSize,
-                     bool hasPixelData);
-      
-      Orthanc::ParsedDicomFile& GetDicom() const
-      {
-        return dicom_;
-      }
-
-      size_t GetFileSize() const
-      {
-        return fileSize_;
-      }
-
-      bool HasPixelData() const
-      {
-        return hasPixelData_;
-      }
-      
-      const std::string& GetSopInstanceUid() const
-      {
-        return sopInstanceUid_;
-      }
-    };
-
-  private:
-    std::string  path_;
-    bool         pixelDataIncluded_;
-
-    ParseDicomFileCommand(const ParseDicomFileCommand& other) :
-      path_(other.path_),
-      pixelDataIncluded_(other.pixelDataIncluded_)
-    {
-    }
-
-  public:
-    ParseDicomFileCommand(const std::string& path) :
-      path_(path),
-      pixelDataIncluded_(true)
-    {
-    }
-
-    ParseDicomFileCommand(const std::string& dicomDirPath,
-                          const std::string& file) :
-      path_(GetDicomDirPath(dicomDirPath, file)),
-      pixelDataIncluded_(true)
-    {
-    }
-
-    static std::string GetDicomDirPath(const std::string& dicomDirPath,
-                                       const std::string& file);
-
-    virtual Type GetType() const
-    {
-      return Type_ParseDicomFile;
-    }
-
-    virtual IOracleCommand* Clone() const
-    {
-      return new ParseDicomFileCommand(*this);
-    }
-
-    const std::string& GetPath() const
-    {
-      return path_;
-    }
-
-    bool IsPixelDataIncluded() const
-    {
-      return pixelDataIncluded_;
-    }
-
-    void SetPixelDataIncluded(bool included)
-    {
-      pixelDataIncluded_ = included;
-    }
-  };
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomFromFileCommand.cpp	Fri Nov 15 17:34:19 2019 +0100
@@ -0,0 +1,43 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ParseDicomFromFileCommand.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/filesystem/path.hpp>
+
+namespace OrthancStone
+{
+  std::string ParseDicomFromFileCommand::GetDicomDirPath(const std::string& dicomDirPath,
+                                                         const std::string& file)
+  {
+    std::string tmp = file;
+
+#if !defined(_WIN32)
+    std::replace(tmp.begin(), tmp.end(), '\\', '/');
+#endif
+
+    boost::filesystem::path base = boost::filesystem::path(dicomDirPath).parent_path();
+
+    return (base / tmp).string();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomFromFileCommand.h	Fri Nov 15 17:34:19 2019 +0100
@@ -0,0 +1,82 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "OracleCommandBase.h"
+
+namespace OrthancStone
+{
+  class ParseDicomFromFileCommand : public OracleCommandBase
+  {
+  private:
+    std::string  path_;
+    bool         pixelDataIncluded_;
+
+    ParseDicomFromFileCommand(const ParseDicomFromFileCommand& other) :
+      path_(other.path_),
+      pixelDataIncluded_(other.pixelDataIncluded_)
+    {
+    }
+
+  public:
+    ParseDicomFromFileCommand(const std::string& path) :
+      path_(path),
+      pixelDataIncluded_(true)
+    {
+    }
+
+    ParseDicomFromFileCommand(const std::string& dicomDirPath,
+                              const std::string& file) :
+      path_(GetDicomDirPath(dicomDirPath, file)),
+      pixelDataIncluded_(true)
+    {
+    }
+    
+    static std::string GetDicomDirPath(const std::string& dicomDirPath,
+                                       const std::string& file);
+
+    virtual Type GetType() const
+    {
+      return Type_ParseDicomFromFile;
+    }
+
+    virtual IOracleCommand* Clone() const
+    {
+      return new ParseDicomFromFileCommand(*this);
+    }
+
+    const std::string& GetPath() const
+    {
+      return path_;
+    }
+
+    bool IsPixelDataIncluded() const
+    {
+      return pixelDataIncluded_;
+    }
+
+    void SetPixelDataIncluded(bool included)
+    {
+      pixelDataIncluded_ = included;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomFromWadoCommand.cpp	Fri Nov 15 17:34:19 2019 +0100
@@ -0,0 +1,58 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ParseDicomFromWadoCommand.h"
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  ParseDicomFromWadoCommand::ParseDicomFromWadoCommand(const std::string& sopInstanceUid,
+                                                       IOracleCommand* restCommand) :
+    sopInstanceUid_(sopInstanceUid),
+    restCommand_(restCommand)
+  {
+    if (restCommand == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+
+    if (restCommand_->GetType() != Type_Http &&
+        restCommand_->GetType() != Type_OrthancRestApi)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }        
+  }
+
+  
+  IOracleCommand* ParseDicomFromWadoCommand::Clone() const
+  {
+    assert(restCommand_.get() != NULL);
+    return new ParseDicomFromWadoCommand(sopInstanceUid_, restCommand_->Clone());
+  }
+
+
+  const IOracleCommand& ParseDicomFromWadoCommand::GetRestCommand() const
+  {
+    assert(restCommand_.get() != NULL);
+    return *restCommand_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomFromWadoCommand.h	Fri Nov 15 17:34:19 2019 +0100
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "OracleCommandBase.h"
+
+namespace OrthancStone
+{
+  class ParseDicomFromWadoCommand : public OracleCommandBase
+  {
+  private:
+    std::string                    sopInstanceUid_;
+    std::auto_ptr<IOracleCommand>  restCommand_;
+
+  public:
+    ParseDicomFromWadoCommand(const std::string& sopInstanceUid,
+                              IOracleCommand* restCommand);
+
+    virtual Type GetType() const
+    {
+      return Type_ParseDicomFromWado;
+    }
+
+    virtual IOracleCommand* Clone() const;
+
+    const std::string& GetSopInstanceUid() const
+    {
+      return sopInstanceUid_;
+    }
+    
+    const IOracleCommand& GetRestCommand() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomSuccessMessage.cpp	Fri Nov 15 17:34:19 2019 +0100
@@ -0,0 +1,107 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ParseDicomSuccessMessage.h"
+
+#include <Core/DicomParsing/ParsedDicomFile.h>
+#include <Core/HttpServer/MultipartStreamReader.h>
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  class MultipartHandler : public Orthanc::MultipartStreamReader::IHandler
+  {
+  private:
+    std::auto_ptr<Orthanc::ParsedDicomFile>  dicom_;
+    size_t                                   size_;
+
+  public:
+    MultipartHandler() :
+      size_(0)
+    {
+    }
+      
+    virtual void HandlePart(const std::map<std::string, std::string>& headers,
+                            const void* part,
+                            size_t size)
+    {
+      if (dicom_.get())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                        "Multiple DICOM instances were contained in a WADO-RS request");
+      }
+      else
+      {
+        dicom_.reset(new Orthanc::ParsedDicomFile(part, size));
+        size_ = size;
+      }
+    }
+
+    Orthanc::ParsedDicomFile* ReleaseDicom()
+    {
+      if (dicom_.get())
+      {
+        return dicom_.release();
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                        "WADO-RS request didn't contain any DICOM instance");
+      }
+    }
+
+    size_t GetSize() const
+    {
+      return size_;
+    }
+  };
+
+  
+  Orthanc::ParsedDicomFile* ParseDicomSuccessMessage::ParseWadoAnswer(
+    size_t& fileSize /* OUT */,
+    const std::string& answer,
+    const std::map<std::string, std::string>& headers)
+  {
+    std::string contentType, subType, boundary, header;
+    if (Orthanc::MultipartStreamReader::GetMainContentType(header, headers) &&
+        Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, header) &&
+        contentType == "multipart/related" &&
+        subType == "application/dicom")
+    {
+      MultipartHandler handler;
+
+      {
+        Orthanc::MultipartStreamReader reader(boundary);
+        reader.SetHandler(handler);
+        reader.AddChunk(answer);
+        reader.CloseStream();
+      }
+
+      fileSize = handler.GetSize();
+      return handler.ReleaseDicom();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                      "Multipart/related answer of application/dicom was expected from DICOMweb server");
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Oracle/ParseDicomSuccessMessage.h	Fri Nov 15 17:34:19 2019 +0100
@@ -0,0 +1,85 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
+#if ORTHANC_ENABLE_DCMTK != 1
+#  error Support for DCMTK must be enabled to use ParseDicomFromFileCommand
+#endif
+
+#include "OracleCommandBase.h"
+#include "../Messages/IMessageEmitter.h"
+#include "../Messages/IObserver.h"
+
+#include <map>
+
+namespace Orthanc
+{
+  class ParsedDicomFile;
+}
+
+namespace OrthancStone
+{
+  class ParseDicomSuccessMessage : public OriginMessage<OracleCommandBase>
+  {
+    ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
+    
+  private:
+    Orthanc::ParsedDicomFile&  dicom_;
+    size_t                     fileSize_;
+    bool                       hasPixelData_;
+    
+  public:
+    ParseDicomSuccessMessage(const OracleCommandBase& command,
+                             Orthanc::ParsedDicomFile& dicom,
+                             size_t fileSize,
+                             bool hasPixelData) :
+      OriginMessage(command),
+      dicom_(dicom),
+      fileSize_(fileSize),
+      hasPixelData_(hasPixelData)
+    {
+    }
+      
+    Orthanc::ParsedDicomFile& GetDicom() const
+    {
+      return dicom_;
+    }
+
+    size_t GetFileSize() const
+    {
+      return fileSize_;
+    }
+
+    bool HasPixelData() const
+    {
+      return hasPixelData_;
+    }
+    
+    static Orthanc::ParsedDicomFile* ParseWadoAnswer(size_t& fileSize /* OUT */,
+                                                     const std::string& answer,
+                                                     const std::map<std::string, std::string>& headers);
+  };
+}
--- a/Framework/Toolbox/ParsedDicomCache.cpp	Fri Nov 15 12:38:15 2019 +0100
+++ b/Framework/Toolbox/ParsedDicomCache.cpp	Fri Nov 15 17:34:19 2019 +0100
@@ -68,30 +68,42 @@
   };
     
 
-  void ParsedDicomCache::Acquire(const std::string& key,
+  std::string ParsedDicomCache::GetIndex(unsigned int bucket,
+                                         const std::string& bucketKey)
+  {
+    return boost::lexical_cast<std::string>(bucket) + "|" + bucketKey;
+  }
+  
+
+  void ParsedDicomCache::Acquire(unsigned int bucket,
+                                 const std::string& bucketKey,
                                  Orthanc::ParsedDicomFile* dicom,
                                  size_t fileSize,
                                  bool hasPixelData)
   {
-    cache_.Acquire(key, new Item(dicom, fileSize, hasPixelData));
+    LOG(TRACE) << "new item stored in cache: bucket " << bucket << ", key " << bucketKey;
+    cache_.Acquire(GetIndex(bucket, bucketKey), new Item(dicom, fileSize, hasPixelData));
   }
 
   
   ParsedDicomCache::Reader::Reader(ParsedDicomCache& cache,
-                                   const std::string& key) :
+                                   unsigned int bucket,
+                                   const std::string& bucketKey) :
     /**
      * The "DcmFileFormat" object cannot be accessed from multiple
      * threads, even if using only getters. An unique lock (mutex) is
      * mandatory.
      **/
-    accessor_(cache.cache_, key, true /* unique */)
+    accessor_(cache.cache_, GetIndex(bucket, bucketKey), true /* unique */)
   {
     if (accessor_.IsValid())
     {
+      LOG(TRACE) << "accessing item within cache: bucket " << bucket << ", key " << bucketKey;
       item_ = &dynamic_cast<Item&>(accessor_.GetValue());
     }
     else
     {
+      LOG(TRACE) << "missing item within cache: bucket " << bucket << ", key " << bucketKey;
       item_ = NULL;
     }
   }
--- a/Framework/Toolbox/ParsedDicomCache.h	Fri Nov 15 12:38:15 2019 +0100
+++ b/Framework/Toolbox/ParsedDicomCache.h	Fri Nov 15 17:34:19 2019 +0100
@@ -30,6 +30,9 @@
   {
   private:
     class Item;
+
+    static std::string GetIndex(unsigned int bucket,
+                                const std::string& bucketKey);
     
     Orthanc::MemoryObjectCache  cache_;
 
@@ -39,12 +42,14 @@
       cache_.SetMaximumSize(size);
     }
 
-    void Invalidate(const std::string& key)
+    void Invalidate(unsigned int bucket,
+                    const std::string& bucketKey)
     {
-      cache_.Invalidate(key);
+      cache_.Invalidate(GetIndex(bucket, bucketKey));
     }
     
-    void Acquire(const std::string& key,
+    void Acquire(unsigned int bucket,
+                 const std::string& bucketKey,
                  Orthanc::ParsedDicomFile* dicom,
                  size_t fileSize,
                  bool hasPixelData);
@@ -57,7 +62,8 @@
 
     public:
       Reader(ParsedDicomCache& cache,
-             const std::string& key);
+             unsigned int bucket,
+             const std::string& bucketKey);
 
       bool IsValid() const
       {
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Nov 15 12:38:15 2019 +0100
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Nov 15 17:34:19 2019 +0100
@@ -401,7 +401,9 @@
 
 if (ENABLE_DCMTK)
   list(APPEND ORTHANC_STONE_SOURCES
-    ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomFileCommand.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomFromFileCommand.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomFromWadoCommand.cpp
+    ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomSuccessMessage.cpp
     ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParsedDicomCache.cpp
     )
 endif()