changeset 1484:121d01aa328e

SeriesThumbnailsLoader working on raw dicom files
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 22 Jun 2020 17:46:40 +0200
parents 6abd819aa534
children 60be4627ae52
files Framework/Loaders/DicomResourcesLoader.cpp Framework/Loaders/DicomResourcesLoader.h Framework/Loaders/DicomSource.cpp Framework/Loaders/OracleScheduler.cpp Framework/Loaders/SeriesFramesLoader.cpp Framework/Loaders/SeriesThumbnailsLoader.cpp Framework/Loaders/SeriesThumbnailsLoader.h Framework/Oracle/GenericOracleRunner.cpp Framework/Oracle/ParseDicomFromFileCommand.h Framework/Oracle/ParseDicomFromWadoCommand.cpp Framework/Oracle/ParseDicomFromWadoCommand.h Framework/Oracle/ParseDicomSuccessMessage.h Framework/Oracle/WebAssemblyOracle.cpp Framework/Oracle/WebAssemblyOracle.h Framework/Toolbox/ImageToolbox.cpp Framework/Toolbox/ImageToolbox.h
diffstat 16 files changed, 391 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Loaders/DicomResourcesLoader.cpp	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Loaders/DicomResourcesLoader.cpp	Mon Jun 22 17:46:40 2020 +0200
@@ -27,6 +27,7 @@
 
 #if ORTHANC_ENABLE_DCMTK == 1
 #  include "../Oracle/ParseDicomFromFileCommand.h"
+#  include <DicomParsing/ParsedDicomDir.h>
 #  include <DicomParsing/ParsedDicomFile.h>
 #endif
 
@@ -864,7 +865,7 @@
     boost::shared_ptr<Orthanc::IDynamicObject> protection(userPayload);
     
 #if ORTHANC_ENABLE_DCMTK == 1
-    std::unique_ptr<ParseDicomFromFileCommand> command(new ParseDicomFromFileCommand(path));
+    std::unique_ptr<ParseDicomFromFileCommand> command(new ParseDicomFromFileCommand(source, path));
     command->SetPixelDataIncluded(includePixelData);
     command->AcquirePayload(new Handler(shared_from_this(), target, priority, source, protection));
 
--- a/Framework/Loaders/DicomResourcesLoader.h	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Loaders/DicomResourcesLoader.h	Mon Jun 22 17:46:40 2020 +0200
@@ -27,11 +27,6 @@
 #  error The macro ORTHANC_ENABLE_DCMTK must be defined
 #endif
 
-#if ORTHANC_ENABLE_DCMTK == 1
-#  include "../Oracle/ParseDicomFromFileCommand.h"
-#  include <DicomParsing/ParsedDicomDir.h>
-#endif
-
 #include "../Oracle/HttpCommand.h"
 #include "../Oracle/OracleCommandExceptionMessage.h"
 #include "../Oracle/OrthancRestApiCommand.h"
@@ -41,8 +36,19 @@
 #include "LoadedDicomResources.h"
 #include "OracleScheduler.h"
 
+namespace Orthanc
+{
+#if ORTHANC_ENABLE_DCMTK == 1
+  class ParsedDicomDir;
+#endif
+}
+
 namespace OrthancStone
 {
+#if ORTHANC_ENABLE_DCMTK == 1
+  class ParseDicomFromFileCommand;
+#endif
+  
   class DicomResourcesLoader :
     public ObserverBase<DicomResourcesLoader>,
     public IObservable
--- a/Framework/Loaders/DicomSource.cpp	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Loaders/DicomSource.cpp	Mon Jun 22 17:46:40 2020 +0200
@@ -190,11 +190,11 @@
         {
           h[it->first] = it->second;
         }
-          
+        
         Json::Value body = Json::objectValue;
         body["Uri"] = uri;
         body["Arguments"] = args;
-        body["Headers"] = h;
+        body["HttpHeaders"] = h;
 
         std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
         command->SetMethod(Orthanc::HttpMethod_Post);
--- a/Framework/Loaders/OracleScheduler.cpp	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Loaders/OracleScheduler.cpp	Mon Jun 22 17:46:40 2020 +0200
@@ -411,7 +411,7 @@
 
     ParseDicomSuccessMessage bis(
       dynamic_cast<const OracleCommandBase&>(payload.GetOriginalCommand()),
-      message.GetDicom(), message.GetFileSize(), message.HasPixelData());
+      message.GetSource(), message.GetDicom(), message.GetFileSize(), message.HasPixelData());
     emitter_.EmitMessage(payload.GetOriginalReceiver(), bis);
   }
 #endif
--- a/Framework/Loaders/SeriesFramesLoader.cpp	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Loaders/SeriesFramesLoader.cpp	Mon Jun 22 17:46:40 2020 +0200
@@ -415,7 +415,7 @@
       std::string file;
       if (dicomDir_->LookupStringValue(file, sopInstanceUid, Orthanc::DICOM_TAG_REFERENCED_FILE_ID))
       {
-        std::unique_ptr<ParseDicomFromFileCommand> command(new ParseDicomFromFileCommand(dicomDirPath_, file));
+        std::unique_ptr<ParseDicomFromFileCommand> command(new ParseDicomFromFileCommand(source, dicomDirPath_, file));
         command->SetPixelDataIncluded(true);
         command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
 
@@ -474,7 +474,7 @@
         const std::map<std::string, std::string> empty;
 
         std::unique_ptr<ParseDicomFromWadoCommand> command(
-          new ParseDicomFromWadoCommand(sopInstanceUid, source.CreateDicomWebCommand(uri, empty, empty, NULL)));
+          new ParseDicomFromWadoCommand(source, sopInstanceUid, source.CreateDicomWebCommand(uri, empty, empty, NULL)));
         command->AcquirePayload(payload.release());
 
         {
--- a/Framework/Loaders/SeriesThumbnailsLoader.cpp	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Loaders/SeriesThumbnailsLoader.cpp	Mon Jun 22 17:46:40 2020 +0200
@@ -21,8 +21,13 @@
 
 #include "SeriesThumbnailsLoader.h"
 
+#include "LoadedDicomResources.h"
+#include "../Oracle/ParseDicomFromWadoCommand.h"
+#include "../Toolbox/ImageToolbox.h"
+
 #include <DicomFormat/DicomMap.h>
 #include <DicomFormat/DicomInstanceHasher.h>
+#include <Images/Image.h>
 #include <Images/ImageProcessing.h>
 #include <Images/JpegReader.h>
 #include <Images/JpegWriter.h>
@@ -30,6 +35,12 @@
 
 #include <boost/algorithm/string/predicate.hpp>
 
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include <DicomParsing/ParsedDicomFile.h>
+#  include <DicomParsing/Internals/DicomImageDecoder.h>
+#endif 
+
+
 static const unsigned int JPEG_QUALITY = 70;  // Only used for Orthanc source
 
 namespace OrthancStone
@@ -111,7 +122,7 @@
     assert(thumbnail != NULL);
   
     std::unique_ptr<Thumbnail> protection(thumbnail);
-  
+
     Thumbnails::iterator found = thumbnails_.find(seriesInstanceUid);
     if (found == thumbnails_.end())
     {
@@ -120,10 +131,21 @@
     else
     {
       assert(found->second != NULL);
-      delete found->second;
-      found->second = protection.release();
+      if (protection->GetType() == SeriesThumbnailType_NotLoaded ||
+          protection->GetType() == SeriesThumbnailType_Unsupported)
+      {
+        // Don't replace an old entry if the current one is worse
+        return;
+      }
+      else
+      {
+        delete found->second;
+        found->second = protection.release();
+      }
     }
 
+    LOG(INFO) << "Thumbnail updated for series: " << seriesInstanceUid << ": " << thumbnail->GetType();
+    
     SuccessMessage message(*this, source, studyInstanceUid, seriesInstanceUid, *thumbnail);
     BroadcastMessage(message);
   }
@@ -284,7 +306,7 @@
       std::map<std::string, std::string> arguments, headers;
       arguments["0020000D"] = GetStudyInstanceUid();
       arguments["0020000E"] = GetSeriesInstanceUid();
-      arguments["includefield"] = "00080016";
+      arguments["includefield"] = "00080016";  // SOP Class UID
       
       std::unique_ptr<IOracleCommand> command(
         GetSource().CreateDicomWebCommand(
@@ -419,6 +441,52 @@
   };
 
     
+#if ORTHANC_ENABLE_DCMTK == 1
+  class SeriesThumbnailsLoader::SelectDicomWebInstanceHandler : public SeriesThumbnailsLoader::Handler
+  {
+  public:
+    SelectDicomWebInstanceHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader,
+                                  const DicomSource& source,
+                                  const std::string& studyInstanceUid,
+                                  const std::string& seriesInstanceUid) :
+      Handler(loader, source, studyInstanceUid, seriesInstanceUid)
+    {
+    }
+
+    virtual void HandleSuccess(const std::string& body,
+                               const std::map<std::string, std::string>& headers)
+    {
+      Json::Value json;
+      Json::Reader reader;
+      if (!reader.parse(body, json) ||
+          json.type() != Json::arrayValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+
+      LoadedDicomResources instances(Orthanc::DICOM_TAG_SOP_INSTANCE_UID);
+      instances.AddFromDicomWeb(json);
+      
+      std::string sopInstanceUid;      
+      if (instances.GetSize() == 0 ||
+          !instances.GetResource(0).LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
+      {
+        LOG(ERROR) << "Series without an instance: " << GetSeriesInstanceUid();
+      }
+      else
+      {
+        GetLoader()->Schedule(
+          ParseDicomFromWadoCommand::Create(
+            GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid(), sopInstanceUid, false,
+            Orthanc::DicomTransferSyntax_LittleEndianExplicit /* useless, as no transcoding */,
+            new ThumbnailInformation(
+              GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid())));
+      }
+    }
+  };
+#endif
+
+    
   void SeriesThumbnailsLoader::Schedule(IOracleCommand* command)
   {
     std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
@@ -443,6 +511,7 @@
   void SeriesThumbnailsLoader::Handle(const GetOrthancImageCommand::SuccessMessage& message)
   {
     assert(message.GetOrigin().HasPayload());
+    const ThumbnailInformation& info = dynamic_cast<ThumbnailInformation&>(message.GetOrigin().GetPayload());
 
     std::unique_ptr<Orthanc::ImageAccessor> resized(Orthanc::ImageProcessing::FitSize(message.GetImage(), width_, height_));
 
@@ -451,12 +520,91 @@
     writer.SetQuality(JPEG_QUALITY);
     writer.WriteToMemory(jpeg, *resized);
 
-    const ThumbnailInformation& info = dynamic_cast<ThumbnailInformation&>(message.GetOrigin().GetPayload());
     AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(),
                      info.GetSeriesInstanceUid(), new Thumbnail(jpeg, Orthanc::MIME_JPEG));      
   }
 
 
+#if ORTHANC_ENABLE_DCMTK == 1
+  void SeriesThumbnailsLoader::Handle(const ParseDicomSuccessMessage& message)
+  {
+    assert(message.GetOrigin().HasPayload());
+    const ParseDicomFromWadoCommand& origin =
+      dynamic_cast<const ParseDicomFromWadoCommand&>(message.GetOrigin());
+    const ThumbnailInformation& info = dynamic_cast<ThumbnailInformation&>(origin.GetPayload());
+
+    std::string tmp;
+    Orthanc::DicomTransferSyntax transferSyntax;
+    if (!message.GetDicom().LookupTransferSyntax(tmp))
+    {
+      
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "DICOM instance without a transfer syntax: " + origin.GetSopInstanceUid());
+    }
+    else if (!Orthanc::LookupTransferSyntax(transferSyntax, tmp) ||
+             !ImageToolbox::IsDecodingSupported(transferSyntax))
+    {
+      LOG(INFO) << "Asking the DICOMweb server to transcode, "
+                << "as I don't support this transfer syntax: " << tmp;
+
+      Schedule(ParseDicomFromWadoCommand::Create(
+                 origin.GetSource(), info.GetStudyInstanceUid(), info.GetSeriesInstanceUid(),
+                 origin.GetSopInstanceUid(), true, Orthanc::DicomTransferSyntax_LittleEndianExplicit,
+                 new ThumbnailInformation(
+                   origin.GetSource(), info.GetStudyInstanceUid(), info.GetSeriesInstanceUid())));
+    }
+    else
+    {
+      std::unique_ptr<Orthanc::ImageAccessor> frame(
+        Orthanc::DicomImageDecoder::Decode(message.GetDicom(), 0));
+
+      std::unique_ptr<Orthanc::ImageAccessor> thumbnail;
+
+      if (frame->GetFormat() == Orthanc::PixelFormat_RGB24)
+      {
+        thumbnail.reset(Orthanc::ImageProcessing::FitSize(*frame, width_, height_));
+      }
+      else
+      {
+        const unsigned int width = frame->GetWidth();
+        const unsigned int height = frame->GetHeight();
+
+        std::unique_ptr<Orthanc::ImageAccessor> converted(
+          new Orthanc::Image(Orthanc::PixelFormat_Float32, width, height, false));
+        Orthanc::ImageProcessing::Convert(*converted, *frame);
+
+        std::unique_ptr<Orthanc::ImageAccessor> resized(
+          Orthanc::ImageProcessing::FitSize(*converted, width, height));
+      
+        float minValue, maxValue;
+        Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *resized);
+        if (minValue + 0.01f < maxValue)
+        {
+          Orthanc::ImageProcessing::ShiftScale(*resized, -minValue, 255.0f / (maxValue - minValue), false);
+        }
+        else
+        {
+          Orthanc::ImageProcessing::Set(*resized, 0);
+        }
+
+        converted.reset(NULL);
+
+        thumbnail.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false));
+        Orthanc::ImageProcessing::Convert(*thumbnail, *resized);
+      }
+    
+      std::string jpeg;
+      Orthanc::JpegWriter writer;
+      writer.SetQuality(JPEG_QUALITY);
+      writer.WriteToMemory(jpeg, *thumbnail);
+
+      AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(),
+                       info.GetSeriesInstanceUid(), new Thumbnail(jpeg, Orthanc::MIME_JPEG));
+    }
+  }
+#endif
+
+
   void SeriesThumbnailsLoader::Handle(const OracleCommandExceptionMessage& message)
   {
     const OracleCommandBase& command = dynamic_cast<const OracleCommandBase&>(message.GetOrigin());
@@ -495,6 +643,11 @@
     result->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
     result->Register<OracleCommandExceptionMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
     result->Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    result->Register<ParseDicomSuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle);
+#endif
+    
     return result;
   }
 
@@ -556,33 +709,48 @@
     {
       return;
     }
-    
+
     if (source.IsDicomWeb())
     {
       if (!source.HasDicomWebRendered())
       {
-        // TODO - Could use DCMTK here
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
-                                        "DICOMweb server is not able to generate renderings of DICOM series");
+#if ORTHANC_ENABLE_DCMTK == 1
+        // Issue a QIDO-RS request to select one of the instances in the series
+        std::map<std::string, std::string> arguments, headers;
+        arguments["0020000D"] = studyInstanceUid;
+        arguments["0020000E"] = seriesInstanceUid;
+        arguments["includefield"] = "00080018";  // SOP Instance UID is mandatory
+        
+        std::unique_ptr<IOracleCommand> command(
+          source.CreateDicomWebCommand(
+            "/instances", arguments, headers, new SelectDicomWebInstanceHandler(
+              GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid)));
+        Schedule(command.release());
+#else
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
+                                        "Stone of Orthanc was built without support to decode DICOM images");
+#endif
       }
-      
-      const std::string uri = ("/studies/" + studyInstanceUid +
-                               "/series/" + seriesInstanceUid + "/rendered");
+      else
+      {
+        const std::string uri = ("/studies/" + studyInstanceUid +
+                                 "/series/" + seriesInstanceUid + "/rendered");
 
-      std::map<std::string, std::string> arguments, headers;
-      arguments["viewport"] = (boost::lexical_cast<std::string>(width_) + "," +
-                               boost::lexical_cast<std::string>(height_));
+        std::map<std::string, std::string> arguments, headers;
+        arguments["viewport"] = (boost::lexical_cast<std::string>(width_) + "," +
+                                 boost::lexical_cast<std::string>(height_));
 
-      // Needed to set this header explicitly, as long as emscripten
-      // does not include macro "EMSCRIPTEN_FETCH_RESPONSE_HEADERS"
-      // https://github.com/emscripten-core/emscripten/pull/8486
-      headers["Accept"] = Orthanc::MIME_JPEG;
+        // Needed to set this header explicitly, as long as emscripten
+        // does not include macro "EMSCRIPTEN_FETCH_RESPONSE_HEADERS"
+        // https://github.com/emscripten-core/emscripten/pull/8486
+        headers["Accept"] = Orthanc::MIME_JPEG;
 
-      std::unique_ptr<IOracleCommand> command(
-        source.CreateDicomWebCommand(
-          uri, arguments, headers, new DicomWebThumbnailHandler(
-            shared_from_this(), source, studyInstanceUid, seriesInstanceUid)));
-      Schedule(command.release());
+        std::unique_ptr<IOracleCommand> command(
+          source.CreateDicomWebCommand(
+            uri, arguments, headers, new DicomWebThumbnailHandler(
+              GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid)));
+        Schedule(command.release());
+      }
 
       scheduledSeries_.insert(seriesInstanceUid);
     }
@@ -594,7 +762,7 @@
       std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
       command->SetUri("/series/" + hasher.HashSeries());
       command->AcquirePayload(new SelectOrthancInstanceHandler(
-                                shared_from_this(), source, studyInstanceUid, seriesInstanceUid));
+                                GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid));
       Schedule(command.release());
 
       scheduledSeries_.insert(seriesInstanceUid);
--- a/Framework/Loaders/SeriesThumbnailsLoader.h	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Loaders/SeriesThumbnailsLoader.h	Mon Jun 22 17:46:40 2020 +0200
@@ -21,6 +21,13 @@
 
 #pragma once
 
+#include "../OrthancStone.h"
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error Macro ORTHANC_ENABLE_DCMTK must be defined
+#endif 
+
+
 #include "../Oracle/GetOrthancImageCommand.h"
 #include "../Oracle/HttpCommand.h"
 #include "../Oracle/OracleCommandExceptionMessage.h"
@@ -141,6 +148,10 @@
     class ThumbnailInformation;
     class OrthancSopClassHandler;
     class SelectOrthancInstanceHandler;
+
+#if ORTHANC_ENABLE_DCMTK == 1
+    class SelectDicomWebInstanceHandler;
+#endif
     
     // Maps a "Series Instance UID" to a thumbnail
     typedef std::map<std::string, Thumbnail*>  Thumbnails;
@@ -165,6 +176,10 @@
 
     void Handle(const GetOrthancImageCommand::SuccessMessage& message);
 
+#if ORTHANC_ENABLE_DCMTK == 1
+    void Handle(const ParseDicomSuccessMessage& message);
+#endif
+
     void Handle(const OracleCommandExceptionMessage& message);
 
     SeriesThumbnailsLoader(ILoadersContext& context,
--- a/Framework/Oracle/GenericOracleRunner.cpp	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Oracle/GenericOracleRunner.cpp	Mon Jun 22 17:46:40 2020 +0200
@@ -345,7 +345,8 @@
            reader.HasPixelData()))
       {
         // Reuse the DICOM file from the cache
-        ParseDicomSuccessMessage message(command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData());
+        ParseDicomSuccessMessage message(command, command.GetSource(), reader.GetDicom(),
+                                         reader.GetFileSize(), reader.HasPixelData());
         emitter.EmitMessage(receiver, message);
         return;
       }
@@ -362,7 +363,8 @@
     
     {
       ParseDicomSuccessMessage message
-        (command, *parsed, static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
+        (command, command.GetSource(), *parsed,
+         static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
       emitter.EmitMessage(receiver, message);
     }
 
@@ -393,7 +395,8 @@
           reader.HasPixelData())
       {
         // Reuse the DICOM file from the cache
-        ParseDicomSuccessMessage message(command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData());
+        ParseDicomSuccessMessage message(command, command.GetSource(), reader.GetDicom(),
+                                         reader.GetFileSize(), reader.HasPixelData());
         emitter.EmitMessage(receiver, message);
         return;
       }
@@ -421,7 +424,7 @@
     std::unique_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, answerHeaders));
 
     {
-      ParseDicomSuccessMessage message(command, *parsed, fileSize,
+      ParseDicomSuccessMessage message(command, command.GetSource(), *parsed, fileSize,
                                        true /* pixel data always is included in WADO-RS */);
       emitter.EmitMessage(receiver, message);
     }
--- a/Framework/Oracle/ParseDicomFromFileCommand.h	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Oracle/ParseDicomFromFileCommand.h	Mon Jun 22 17:46:40 2020 +0200
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "OracleCommandBase.h"
+#include "../Loaders/DicomSource.h"
 
 #include <string>
 
@@ -30,24 +31,30 @@
   class ParseDicomFromFileCommand : public OracleCommandBase
   {
   private:
+    DicomSource  source_;
     std::string  path_;
     bool         pixelDataIncluded_;
 
     ParseDicomFromFileCommand(const ParseDicomFromFileCommand& other) :
+      source_(other.source_),
       path_(other.path_),
       pixelDataIncluded_(other.pixelDataIncluded_)
     {
     }
 
   public:
-    ParseDicomFromFileCommand(const std::string& path) :
+    ParseDicomFromFileCommand(const DicomSource& source,
+                              const std::string& path) :
+      source_(source),
       path_(path),
       pixelDataIncluded_(true)
     {
     }
 
-    ParseDicomFromFileCommand(const std::string& dicomDirPath,
+    ParseDicomFromFileCommand(const DicomSource& source,
+                              const std::string& dicomDirPath,
                               const std::string& file) :
+      source_(source),
       path_(GetDicomDirPath(dicomDirPath, file)),
       pixelDataIncluded_(true)
     {
@@ -66,6 +73,11 @@
       return new ParseDicomFromFileCommand(*this);
     }
 
+    const DicomSource& GetSource() const
+    {
+      return source_;
+    }
+
     const std::string& GetPath() const
     {
       return path_;
--- a/Framework/Oracle/ParseDicomFromWadoCommand.cpp	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Oracle/ParseDicomFromWadoCommand.cpp	Mon Jun 22 17:46:40 2020 +0200
@@ -25,8 +25,10 @@
 
 namespace OrthancStone
 {
-  ParseDicomFromWadoCommand::ParseDicomFromWadoCommand(const std::string& sopInstanceUid,
+  ParseDicomFromWadoCommand::ParseDicomFromWadoCommand(const DicomSource& source,
+                                                       const std::string& sopInstanceUid,
                                                        IOracleCommand* restCommand) :
+    source_(source),
     sopInstanceUid_(sopInstanceUid),
     restCommand_(restCommand)
   {
@@ -46,7 +48,7 @@
   IOracleCommand* ParseDicomFromWadoCommand::Clone() const
   {
     assert(restCommand_.get() != NULL);
-    return new ParseDicomFromWadoCommand(sopInstanceUid_, restCommand_->Clone());
+    return new ParseDicomFromWadoCommand(source_, sopInstanceUid_, restCommand_->Clone());
   }
 
 
@@ -55,4 +57,47 @@
     assert(restCommand_.get() != NULL);
     return *restCommand_;
   }
+
+
+  ParseDicomFromWadoCommand* ParseDicomFromWadoCommand::Create(
+    const DicomSource& source,
+    const std::string& studyInstanceUid,
+    const std::string& seriesInstanceUid,
+    const std::string& sopInstanceUid,
+    bool transcode,
+    Orthanc::DicomTransferSyntax transferSyntax,
+    Orthanc::IDynamicObject* payload)
+  {
+    std::unique_ptr<Orthanc::IDynamicObject> protection(payload);
+    
+    const std::string uri = ("/studies/" + studyInstanceUid +
+                             "/series/" + seriesInstanceUid +
+                             "/instances/" + sopInstanceUid);
+
+    std::string s;
+    if (transcode)
+    {
+      s = Orthanc::GetTransferSyntaxUid(transferSyntax);      
+    }
+    else
+    {
+      s = "*";  // No transcoding, keep source transfer syntax
+    }
+    
+    std::map<std::string, std::string> arguments, headers;
+    headers["Accept"] = ("multipart/related; type=\"application/dicom\"; transfer-syntax=" + s);
+                         
+    std::unique_ptr<IOracleCommand> rest(
+      source.CreateDicomWebCommand(uri, arguments, headers, NULL));
+
+    std::unique_ptr<ParseDicomFromWadoCommand> command(
+      new ParseDicomFromWadoCommand(source, sopInstanceUid, rest.release()));
+
+    if (protection.get() != NULL)
+    {
+      command->AcquirePayload(protection.release());
+    }
+                            
+    return command.release();
+  }
 }
--- a/Framework/Oracle/ParseDicomFromWadoCommand.h	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Oracle/ParseDicomFromWadoCommand.h	Mon Jun 22 17:46:40 2020 +0200
@@ -22,6 +22,9 @@
 #pragma once
 
 #include "OracleCommandBase.h"
+#include "../Loaders/DicomSource.h"
+
+#include <Enumerations.h>
 
 #include <string>
 
@@ -30,11 +33,13 @@
   class ParseDicomFromWadoCommand : public OracleCommandBase
   {
   private:
-    std::string                    sopInstanceUid_;
+    DicomSource                      source_;
+    std::string                      sopInstanceUid_;
     std::unique_ptr<IOracleCommand>  restCommand_;
 
   public:
-    ParseDicomFromWadoCommand(const std::string& sopInstanceUid,
+    ParseDicomFromWadoCommand(const DicomSource& source,
+                              const std::string& sopInstanceUid,
                               IOracleCommand* restCommand);
 
     virtual Type GetType() const
@@ -44,11 +49,24 @@
 
     virtual IOracleCommand* Clone() const;
 
+    const DicomSource& GetSource() const
+    {
+      return source_;
+    }
+    
     const std::string& GetSopInstanceUid() const
     {
       return sopInstanceUid_;
     }
     
     const IOracleCommand& GetRestCommand() const;
+
+    static ParseDicomFromWadoCommand* Create(const DicomSource& source,
+                                             const std::string& studyInstanceUid,
+                                             const std::string& seriesInstanceUid,
+                                             const std::string& sopInstanceUid,
+                                             bool transcode,
+                                             Orthanc::DicomTransferSyntax transferSyntax,
+                                             Orthanc::IDynamicObject* payload);
   };
 }
--- a/Framework/Oracle/ParseDicomSuccessMessage.h	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Oracle/ParseDicomSuccessMessage.h	Mon Jun 22 17:46:40 2020 +0200
@@ -44,27 +44,37 @@
 
 namespace OrthancStone
 {
+  class DicomSource;
+  
   class ParseDicomSuccessMessage : public OriginMessage<OracleCommandBase>
   {
     ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
     
   private:
+    const DicomSource&         source_;
     Orthanc::ParsedDicomFile&  dicom_;
     size_t                     fileSize_;
     bool                       hasPixelData_;
     
   public:
     ParseDicomSuccessMessage(const OracleCommandBase& command,
+                             const DicomSource& source,
                              Orthanc::ParsedDicomFile& dicom,
                              size_t fileSize,
                              bool hasPixelData) :
       OriginMessage(command),
+      source_(source),
       dicom_(dicom),
       fileSize_(fileSize),
       hasPixelData_(hasPixelData)
     {
     }
-      
+
+    const DicomSource& GetSource() const
+    {
+      return source_;
+    }
+
     Orthanc::ParsedDicomFile& GetDicom() const
     {
       return dicom_;
--- a/Framework/Oracle/WebAssemblyOracle.cpp	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Oracle/WebAssemblyOracle.cpp	Mon Jun 22 17:46:40 2020 +0200
@@ -29,6 +29,12 @@
 static unsigned int BUCKET_SOP = 1;
 #endif
 
+#include "GetOrthancImageCommand.h"
+#include "GetOrthancWebViewerJpegCommand.h"
+#include "HttpCommand.h"
+#include "OrthancRestApiCommand.h"
+#include "ParseDicomFromWadoCommand.h"
+
 #include <OrthancException.h>
 #include <Toolbox.h>
 
@@ -305,7 +311,7 @@
                 (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers));
 
               {
-                ParseDicomSuccessMessage message(command, *dicom, fileSize, true);
+                ParseDicomSuccessMessage message(command, command.GetSource(), *dicom, fileSize, true);
                 context->EmitMessage(message);
               }
 
@@ -674,7 +680,7 @@
           reader.HasPixelData())
       {
         // Reuse the DICOM file from the cache
-        ParseDicomSuccessMessage message(*protection, reader.GetDicom(),
+        ParseDicomSuccessMessage message(*protection, protection->GetSource(), reader.GetDicom(),
                                          reader.GetFileSize(), reader.HasPixelData());
         EmitMessage(receiver, message);
         return;
--- a/Framework/Oracle/WebAssemblyOracle.h	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Oracle/WebAssemblyOracle.h	Mon Jun 22 17:46:40 2020 +0200
@@ -32,12 +32,8 @@
 #endif
 
 #include "../Messages/IObservable.h"
-#include "GetOrthancImageCommand.h"
-#include "GetOrthancWebViewerJpegCommand.h"
-#include "HttpCommand.h"
+#include "../Messages/IMessageEmitter.h"
 #include "IOracle.h"
-#include "OrthancRestApiCommand.h"
-#include "ParseDicomFromWadoCommand.h"
 
 #if ORTHANC_ENABLE_DCMTK == 1
 #  include "../Toolbox/ParsedDicomCache.h"
@@ -48,6 +44,12 @@
 
 namespace OrthancStone
 {
+  class GetOrthancImageCommand;
+  class GetOrthancWebViewerJpegCommand;
+  class HttpCommand;
+  class OrthancRestApiCommand;
+  class ParseDicomFromWadoCommand;
+  
   class WebAssemblyOracle :
     public IOracle,
     public IMessageEmitter
--- a/Framework/Toolbox/ImageToolbox.cpp	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Toolbox/ImageToolbox.cpp	Mon Jun 22 17:46:40 2020 +0200
@@ -18,6 +18,7 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
+#include "../OrthancStone.h"
 #include "ImageToolbox.h"
 
 #include "../StoneException.h"
@@ -33,6 +34,19 @@
 
 #include <vector>
 
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error ORTHANC_ENABLE_DCMTK is not defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG)
+#  error ORTHANC_ENABLE_DCMTK_JPEG is not defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS)
+#  error ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS is not defined
+#endif
+
+
 namespace OrthancStone
 {
   namespace
@@ -289,4 +303,35 @@
     ss << "total pix. count: " << pixCount << "\n";
     s = ss.str();
   }
+
+
+  bool ImageToolbox::IsDecodingSupported(Orthanc::DicomTransferSyntax& transferSyntax)
+  {
+    switch (transferSyntax)
+    {
+      case Orthanc::DicomTransferSyntax_LittleEndianImplicit:
+      case Orthanc::DicomTransferSyntax_LittleEndianExplicit:
+      case Orthanc::DicomTransferSyntax_DeflatedLittleEndianExplicit:
+      case Orthanc::DicomTransferSyntax_BigEndianExplicit:
+      case Orthanc::DicomTransferSyntax_RLELossless:
+        return true;
+
+#if (ORTHANC_ENABLE_DCMTK == 1) && (ORTHANC_ENABLE_DCMTK_JPEG == 1)
+      case Orthanc::DicomTransferSyntax_JPEGProcess1:
+      case Orthanc::DicomTransferSyntax_JPEGProcess2_4:
+      case Orthanc::DicomTransferSyntax_JPEGProcess14:
+      case Orthanc::DicomTransferSyntax_JPEGProcess14SV1:
+        return true;
+#endif
+      
+#if (ORTHANC_ENABLE_DCMTK == 1) && (ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1)
+      case Orthanc::DicomTransferSyntax_JPEGLSLossless:
+      case Orthanc::DicomTransferSyntax_JPEGLSLossy:
+        return true;
+#endif
+      
+      default:
+        return false;
+    }
+  }
 }
--- a/Framework/Toolbox/ImageToolbox.h	Sat Jun 20 11:16:55 2020 +0200
+++ b/Framework/Toolbox/ImageToolbox.h	Mon Jun 22 17:46:40 2020 +0200
@@ -73,4 +73,10 @@
   void ComputeMinMax(const Orthanc::ImageAccessor& img, 
                      double& minValue, double& maxValue);
 
+
+  class ImageToolbox
+  {
+  public:
+    static bool IsDecodingSupported(Orthanc::DicomTransferSyntax& transferSyntax);
+  };
 }