changeset 257:9afafb192180 am-2

using PAM
author am@osimis.io
date Tue, 10 Jul 2018 12:39:01 +0200
parents 65562a28fe05
children e5a9b3d03478
files Applications/Samples/SimpleViewerApplication.h Framework/Layers/OrthancFrameLayerSource.cpp Framework/StoneEnumerations.h Framework/Toolbox/IWebService.h Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.h Framework/Volumes/StructureSetLoader.cpp Platforms/Generic/OracleWebService.h Platforms/Generic/WebServiceCommandBase.cpp Platforms/Generic/WebServiceCommandBase.h Platforms/Generic/WebServiceGetCommand.cpp Platforms/Generic/WebServiceGetCommand.h Platforms/Generic/WebServicePostCommand.cpp Platforms/Generic/WebServicePostCommand.h Platforms/Wasm/CMakeLists.txt Platforms/Wasm/WasmWebService.cpp Platforms/Wasm/WasmWebService.h Platforms/Wasm/WasmWebService.js UnitTestsSources/UnitTestsMain.cpp
diffstat 19 files changed, 217 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SimpleViewerApplication.h	Tue Jul 03 13:19:56 2018 +0200
+++ b/Applications/Samples/SimpleViewerApplication.h	Tue Jul 10 12:39:01 2018 +0200
@@ -277,15 +277,18 @@
         // sources
         source_ = new OrthancFrameLayerSource(broker_, context_->GetWebService());
         source_->RegisterObserver(*this);
+        source_->SetImageQuality(SliceImageQuality_FullPam);
         source_->LoadFrame(instances_[currentInstanceIndex_], 0);
 
         mainViewport_->AddLayer(source_);
 
         OrthancFrameLayerSource* thumb0 = new OrthancFrameLayerSource(broker_, context_->GetWebService());
         thumb0->RegisterObserver(*this);
+        thumb0->SetImageQuality(SliceImageQuality_FullPam);
         thumb0->LoadFrame(instances_[0], 0);
         OrthancFrameLayerSource* thumb1 = new OrthancFrameLayerSource(broker_, context_->GetWebService());
         thumb1->RegisterObserver(*this);
+        thumb1->SetImageQuality(SliceImageQuality_FullPam);
         thumb1->LoadFrame(instances_[1], 0);
 
         thumbnails_[0]->AddLayer(thumb0);
@@ -312,6 +315,7 @@
 
         std::auto_ptr<OrthancFrameLayerSource> layer
             (new OrthancFrameLayerSource(broker_, context_->GetWebService()));
+        layer->SetImageQuality(SliceImageQuality_FullPam);
         layer->RegisterObserver(*this);
         layer->LoadFrame(instances_[currentInstanceIndex_], 0);
 
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Tue Jul 03 13:19:56 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Tue Jul 10 12:39:01 2018 +0200
@@ -54,7 +54,7 @@
                                                       std::auto_ptr<Orthanc::ImageAccessor>& image,
                                                       SliceImageQuality quality)
   {
-    bool isFull = (quality == SliceImageQuality_Full);
+    bool isFull = (quality == SliceImageQuality_FullPng || quality == SliceImageQuality_FullPam);
     LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(), slice, isFull),
                                       slice.GetGeometry(), false);
   }
@@ -72,7 +72,7 @@
     LayerSourceBase(broker),
     OrthancSlicesLoader::ISliceLoaderObserver(broker),
     loader_(broker, *this, orthanc),
-    quality_(SliceImageQuality_Full)
+    quality_(SliceImageQuality_FullPng)
   {
   }
 
--- a/Framework/StoneEnumerations.h	Tue Jul 03 13:19:56 2018 +0200
+++ b/Framework/StoneEnumerations.h	Tue Jul 10 12:39:01 2018 +0200
@@ -77,10 +77,13 @@
 
   enum SliceImageQuality
   {
-    SliceImageQuality_Full,
+    SliceImageQuality_FullPng,  // smaller to transmit but longer to generate on Orthanc side (better choice when on low bandwidth)
+    SliceImageQuality_FullPam,  // bigger to transmit but faster to generate on Orthanc side (better choice when on localhost or LAN)
     SliceImageQuality_Jpeg50,
     SliceImageQuality_Jpeg90,
-    SliceImageQuality_Jpeg95
+    SliceImageQuality_Jpeg95,
+
+    SliceImageQuality_InternalRaw   // downloads the raw pixels data as they are stored in the DICOM file (internal use only)
   };
 
   enum SopClassUid
--- a/Framework/Toolbox/IWebService.h	Tue Jul 03 13:19:56 2018 +0200
+++ b/Framework/Toolbox/IWebService.h	Tue Jul 10 12:39:01 2018 +0200
@@ -33,6 +33,8 @@
     protected:
         MessageBroker& broker_;
     public:
+        typedef std::map<std::string, std::string> Headers;
+
         class ICallback : public IObserver
         {
         public:
@@ -115,10 +117,12 @@
 
         virtual void ScheduleGetRequest(ICallback& callback,
                                         const std::string& uri,
+                                        const Headers& headers,
                                         Orthanc::IDynamicObject* payload) = 0;
 
         virtual void SchedulePostRequest(ICallback& callback,
                                          const std::string& uri,
+                                         const Headers& headers,
                                          const std::string& body,
                                          Orthanc::IDynamicObject* payload) = 0;
     };
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Jul 03 13:19:56 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Jul 10 12:39:01 2018 +0200
@@ -29,6 +29,7 @@
 #include <Core/Images/ImageProcessing.h>
 #include <Core/Images/JpegReader.h>
 #include <Core/Images/PngReader.h>
+#include <Core/Images/PamReader.h>
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
 #include <Core/Toolbox.h>
@@ -163,9 +164,17 @@
       std::auto_ptr<Operation> tmp(new Operation(Mode_LoadRawImage));
       tmp->sliceIndex_ = sliceIndex;
       tmp->slice_ = &slice;
-      tmp->quality_ = SliceImageQuality_Full;
+      tmp->quality_ = SliceImageQuality_InternalRaw;
       return tmp.release();
     }
+
+    static Operation* DownloadDicomFile(const Slice&  slice)
+    {
+      std::auto_ptr<Operation> tmp(new Operation(Mode_LoadDicomFile));
+      tmp->slice_ = &slice;
+      return tmp.release();
+    }
+
   };
 
   
@@ -206,10 +215,13 @@
       case Mode_LoadImage:
         switch (operation->GetQuality())
         {
-        case SliceImageQuality_Full:
+        case SliceImageQuality_FullPng:
           that_.ParseSliceImagePng(*operation, answer, answerSize);
           break;
-          
+        case SliceImageQuality_FullPam:
+          that_.ParseSliceImagePam(*operation, answer, answerSize);
+          break;
+
         case SliceImageQuality_Jpeg50:
         case SliceImageQuality_Jpeg90:
         case SliceImageQuality_Jpeg95:
@@ -509,7 +521,48 @@
     NotifySliceImageSuccess(operation, image);
   }
   
-  
+  void OrthancSlicesLoader::ParseSliceImagePam(const Operation& operation,
+                                               const void* answer,
+                                               size_t size)
+  {
+    std::auto_ptr<Orthanc::ImageAccessor>  image;
+
+    try
+    {
+      image.reset(new Orthanc::PamReader);
+      dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(std::string(reinterpret_cast<const char*>(answer), size));
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    if (image->GetWidth() != operation.GetSlice().GetWidth() ||
+        image->GetHeight() != operation.GetSlice().GetHeight())
+    {
+      NotifySliceImageError(operation);
+      return;
+    }
+
+    if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
+        Orthanc::PixelFormat_SignedGrayscale16)
+    {
+      if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
+      {
+        image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+      }
+      else
+      {
+        NotifySliceImageError(operation);
+        return;
+      }
+    }
+
+    NotifySliceImageSuccess(operation, image);
+  }
+
+
   void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation,
                                                 const void* answer,
                                                 size_t size)
@@ -685,7 +738,6 @@
     }
   };
   
-  
   void OrthancSlicesLoader::ParseSliceRawImage(const Operation& operation,
                                                const void* answer,
                                                size_t size)
@@ -768,7 +820,7 @@
     {
       state_ = State_LoadingGeometry;
       std::string uri = "/series/" + seriesId + "/instances-tags";
-      orthanc_.ScheduleGetRequest(*webCallback_, uri, Operation::DownloadSeriesGeometry());
+      orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), Operation::DownloadSeriesGeometry());
     }
   }
   
@@ -787,7 +839,7 @@
       // mandatory to read RT DOSE, but is too long to be returned by default
       std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3004-000c";
       orthanc_.ScheduleGetRequest
-          (*webCallback_, uri, Operation::DownloadInstanceGeometry(instanceId));
+          (*webCallback_, uri, IWebService::Headers(), Operation::DownloadInstanceGeometry(instanceId));
     }
   }
   
@@ -804,7 +856,7 @@
       state_ = State_LoadingGeometry;
       std::string uri = "/instances/" + instanceId + "/tags";
       orthanc_.ScheduleGetRequest
-          (*webCallback_, uri, Operation::DownloadFrameGeometry(instanceId, frame));
+          (*webCallback_, uri, IWebService::Headers(), Operation::DownloadFrameGeometry(instanceId, frame));
     }
   }
   
@@ -873,10 +925,43 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
     
-    orthanc_.ScheduleGetRequest(*webCallback_, uri,
-                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_Full));
+    IWebService::Headers headers;
+    headers["Accept"] = "image/png";
+    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
+                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPng));
   }
   
+  void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice,
+                                                  size_t index)
+  {
+    std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
+                       boost::lexical_cast<std::string>(slice.GetFrame()));
+
+    switch (slice.GetConverter().GetExpectedPixelFormat())
+    {
+    case Orthanc::PixelFormat_RGB24:
+      uri += "/preview";
+      break;
+
+    case Orthanc::PixelFormat_Grayscale16:
+      uri += "/image-uint16";
+      break;
+
+    case Orthanc::PixelFormat_SignedGrayscale16:
+      uri += "/image-int16";
+      break;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    IWebService::Headers headers;
+    headers["Accept"] = "image/x-portable-arbitrarymap";
+    orthanc_.ScheduleGetRequest(*webCallback_, uri, headers,
+                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPam));
+  }
+
+
   
   void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice,
                                                    size_t index,
@@ -908,7 +993,7 @@
                        "-" + slice.GetOrthancInstanceId() + "_" +
                        boost::lexical_cast<std::string>(slice.GetFrame()));
 
-    orthanc_.ScheduleGetRequest(*webCallback_, uri,
+    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
                                 Operation::DownloadSliceImage(index, slice, quality));
   }
   
@@ -926,12 +1011,15 @@
     
     if (slice.HasOrthancDecoding())
     {
-      if (quality == SliceImageQuality_Full)
+      switch (quality)
       {
+      case SliceImageQuality_FullPng:
         ScheduleSliceImagePng(slice, index);
-      }
-      else
-      {
+        break;
+      case SliceImageQuality_FullPam:
+        ScheduleSliceImagePam(slice, index);
+        break;
+      default:
         ScheduleSliceImageJpeg(slice, index, quality);
       }
     }
@@ -939,7 +1027,7 @@
     {
       std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
                          boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz");
-      orthanc_.ScheduleGetRequest(*webCallback_, uri,
+      orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
                                   Operation::DownloadSliceRawImage(index, slice));
     }
   }
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Tue Jul 03 13:19:56 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Tue Jul 10 12:39:01 2018 +0200
@@ -116,7 +116,8 @@
       Mode_InstanceGeometry,
       Mode_FrameGeometry,
       Mode_LoadImage,
-      Mode_LoadRawImage
+      Mode_LoadRawImage,
+      Mode_LoadDicomFile
     };
 
     class Operation;
@@ -150,6 +151,10 @@
                             const void* answer,
                             size_t size);
 
+    void ParseSliceImagePam(const Operation& operation,
+                            const void* answer,
+                            size_t size);
+
     void ParseSliceImageJpeg(const Operation& operation,
                              const void* answer,
                              size_t size);
@@ -160,7 +165,10 @@
 
     void ScheduleSliceImagePng(const Slice& slice,
                                size_t index);
-    
+
+    void ScheduleSliceImagePam(const Slice& slice,
+                               size_t index);
+
     void ScheduleSliceImageJpeg(const Slice& slice,
                                 size_t index,
                                 SliceImageQuality quality);
--- a/Framework/Volumes/StructureSetLoader.cpp	Tue Jul 03 13:19:56 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.cpp	Tue Jul 10 12:39:01 2018 +0200
@@ -88,7 +88,7 @@
         for (std::set<std::string>::const_iterator it = instances.begin();
              it != instances.end(); ++it)
         {
-          orthanc_.SchedulePostRequest(*this, "/tools/lookup", *it,
+          orthanc_.SchedulePostRequest(*this, "/tools/lookup", IWebService::Headers(), *it,
                                        new Operation(Operation::Type_LookupSopInstanceUid, *it));
         }
         
@@ -115,7 +115,7 @@
           }
 
           const std::string& instance = lookup[0]["ID"].asString();
-          orthanc_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags",
+          orthanc_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags", IWebService::Headers(),
                                       new Operation(Operation::Type_LoadReferencedSlice, instance));
         }
         else
@@ -161,7 +161,7 @@
     else
     {
       const std::string uri = "/instances/" + instance + "/tags?ignore-length=3006-0050";
-      orthanc_.ScheduleGetRequest(*this, uri, new Operation(Operation::Type_LoadStructureSet, instance));
+      orthanc_.ScheduleGetRequest(*this, uri, IWebService::Headers(), new Operation(Operation::Type_LoadStructureSet, instance));
     }
   }
 
--- a/Platforms/Generic/OracleWebService.h	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Generic/OracleWebService.h	Tue Jul 10 12:39:01 2018 +0200
@@ -46,17 +46,19 @@
 
     virtual void ScheduleGetRequest(ICallback& callback,
                                     const std::string& uri,
+                                    const Headers& headers,
                                     Orthanc::IDynamicObject* payload)
     {
-      oracle_.Submit(new WebServiceGetCommand(broker_, callback, parameters_, uri, payload));
+      oracle_.Submit(new WebServiceGetCommand(broker_, callback, parameters_, uri, headers, payload));
     }
 
     virtual void SchedulePostRequest(ICallback& callback,
                                      const std::string& uri,
+                                     const Headers& headers,
                                      const std::string& body,
                                      Orthanc::IDynamicObject* payload)
     {
-      oracle_.Submit(new WebServicePostCommand(broker_, callback, parameters_, uri, body, payload));
+      oracle_.Submit(new WebServicePostCommand(broker_, callback, parameters_, uri, headers, body, payload));
     }
 
     void Start()
--- a/Platforms/Generic/WebServiceCommandBase.cpp	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Generic/WebServiceCommandBase.cpp	Tue Jul 10 12:39:01 2018 +0200
@@ -29,11 +29,13 @@
                                              IWebService::ICallback& callback,
                                              const Orthanc::WebServiceParameters& parameters,
                                              const std::string& uri,
+                                             const IWebService::Headers& headers,
                                              Orthanc::IDynamicObject* payload /* takes ownership */) :
     IObservable(broker),
     callback_(callback),
     parameters_(parameters),
     uri_(uri),
+    headers_(headers),
     payload_(payload)
   {
     RegisterObserver(callback);
--- a/Platforms/Generic/WebServiceCommandBase.h	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Generic/WebServiceCommandBase.h	Tue Jul 10 12:39:01 2018 +0200
@@ -38,6 +38,7 @@
     IWebService::ICallback&                 callback_;
     Orthanc::WebServiceParameters           parameters_;
     std::string                             uri_;
+    std::map<std::string, std::string>      headers_;
     std::auto_ptr<Orthanc::IDynamicObject>  payload_;
     bool                                    success_;
     std::string                             answer_;
@@ -47,6 +48,7 @@
                          IWebService::ICallback& callback,
                          const Orthanc::WebServiceParameters& parameters,
                          const std::string& uri,
+                         const std::map<std::string, std::string>& headers,
                          Orthanc::IDynamicObject* payload /* takes ownership */);
 
     virtual void Execute() = 0;
--- a/Platforms/Generic/WebServiceGetCommand.cpp	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.cpp	Tue Jul 10 12:39:01 2018 +0200
@@ -29,8 +29,9 @@
                                              IWebService::ICallback& callback,
                                              const Orthanc::WebServiceParameters& parameters,
                                              const std::string& uri,
+                                             const IWebService::Headers& headers,
                                              Orthanc::IDynamicObject* payload /* takes ownership */) :
-    WebServiceCommandBase(broker, callback, parameters, uri, payload)
+    WebServiceCommandBase(broker, callback, parameters, uri, headers, payload)
   {
   }
 
@@ -40,6 +41,12 @@
     Orthanc::HttpClient client(parameters_, uri_);
     client.SetTimeout(60);
     client.SetMethod(Orthanc::HttpMethod_Get);
+
+    for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ )
+    {
+      client.AddHeader(it->first, it->second);
+    }
+
     success_ = client.Apply(answer_);
   }
 
--- a/Platforms/Generic/WebServiceGetCommand.h	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.h	Tue Jul 10 12:39:01 2018 +0200
@@ -32,6 +32,7 @@
                          IWebService::ICallback& callback,
                          const Orthanc::WebServiceParameters& parameters,
                          const std::string& uri,
+                         const IWebService::Headers& headers,
                          Orthanc::IDynamicObject* payload /* takes ownership */);
 
     virtual void Execute();
--- a/Platforms/Generic/WebServicePostCommand.cpp	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.cpp	Tue Jul 10 12:39:01 2018 +0200
@@ -29,9 +29,10 @@
                                                IWebService::ICallback& callback,
                                                const Orthanc::WebServiceParameters& parameters,
                                                const std::string& uri,
+                                               const IWebService::Headers& headers,
                                                const std::string& body,
                                                Orthanc::IDynamicObject* payload /* takes ownership */) :
-    WebServiceCommandBase(broker, callback, parameters, uri, payload),
+    WebServiceCommandBase(broker, callback, parameters, uri, headers, payload),
     body_(body)
   {
   }
@@ -42,6 +43,12 @@
     client.SetTimeout(60);
     client.SetMethod(Orthanc::HttpMethod_Post);
     client.GetBody().swap(body_);
+
+    for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ )
+    {
+      client.AddHeader(it->first, it->second);
+    }
+
     success_ = client.Apply(answer_);
   }
 
--- a/Platforms/Generic/WebServicePostCommand.h	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.h	Tue Jul 10 12:39:01 2018 +0200
@@ -35,6 +35,7 @@
                           IWebService::ICallback& callback,
                           const Orthanc::WebServiceParameters& parameters,
                           const std::string& uri,
+                          const IWebService::Headers& headers,
                           const std::string& body,
                           Orthanc::IDynamicObject* payload /* takes ownership */);
 
--- a/Platforms/Wasm/CMakeLists.txt	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Wasm/CMakeLists.txt	Tue Jul 10 12:39:01 2018 +0200
@@ -44,12 +44,12 @@
 add_custom_command(
     OUTPUT "${AUTOGENERATED_DIR}/WasmWebService.c"
     COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/WasmWebService.c" ""
-    DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/WebAssembly/WasmWebService.js")
+    DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmWebService.js")
 
 add_custom_command(
     OUTPUT "${AUTOGENERATED_DIR}/default-library.c"
     COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/default-library.c" ""
-    DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/WebAssembly/default-library.js")
+    DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/default-library.js")
 
 
 #####################################################################
@@ -63,6 +63,9 @@
   add_executable(${Target}
     ${STONE_WASM_SOURCES}
 
+    ${AUTOGENERATED_DIR}/WasmWebService.c
+    ${AUTOGENERATED_DIR}/default-library.c
+
     ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainWasm.cpp
 #    ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationContext.cpp
     ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h
--- a/Platforms/Wasm/WasmWebService.cpp	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Wasm/WasmWebService.cpp	Tue Jul 10 12:39:01 2018 +0200
@@ -1,5 +1,6 @@
 #include "WasmWebService.h"
-
+#include "json/value.h"
+#include "json/writer.h"
 #include <emscripten/emscripten.h>
 
 #ifdef __cplusplus
@@ -8,10 +9,12 @@
 
   extern void WasmWebService_ScheduleGetRequest(void* callback,
                                                 const char* uri,
+                                                const char* headersInJsonString,
                                                 void* payload);
   
   extern void WasmWebService_SchedulePostRequest(void* callback,
                                                  const char* uri,
+                                                 const char* headersInJsonString,
                                                  const void* body,
                                                  size_t bodySize,
                                                  void* payload);
@@ -77,21 +80,43 @@
     }
   }
 
+  void ToJsonString(std::string& output, const IWebService::Headers& headers)
+  {
+    Json::Value jsonHeaders;
+    for (IWebService::Headers::const_iterator it = headers.begin(); it != headers.end(); it++ )
+    {
+      jsonHeaders[it->first] = it->second;
+    }
+
+    Json::StreamWriterBuilder builder;
+    std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
+    std::ostringstream outputStr;
+
+    writer->write(jsonHeaders, &outputStr);
+    output = outputStr.str();
+  }
+
   void WasmWebService::ScheduleGetRequest(ICallback& callback,
                                           const std::string& relativeUri,
+                                          const Headers& headers,
                                           Orthanc::IDynamicObject* payload)
   {
     std::string uri = baseUri_ + relativeUri;
-    WasmWebService_ScheduleGetRequest(&callback, uri.c_str(), payload);
+    std::string headersInJsonString;
+    ToJsonString(headersInJsonString, headers);
+    WasmWebService_ScheduleGetRequest(&callback, uri.c_str(), headersInJsonString.c_str(), payload);
   }
 
   void WasmWebService::SchedulePostRequest(ICallback& callback,
                                            const std::string& relativeUri,
+                                           const Headers& headers,
                                            const std::string& body,
                                            Orthanc::IDynamicObject* payload)
   {
     std::string uri = baseUri_ + relativeUri;
-    WasmWebService_SchedulePostRequest(&callback, uri.c_str(),
+    std::string headersInJsonString;
+    ToJsonString(headersInJsonString, headers);
+    WasmWebService_SchedulePostRequest(&callback, uri.c_str(), headersInJsonString.c_str(),
                                        body.c_str(), body.size(), payload);
   }
 }
--- a/Platforms/Wasm/WasmWebService.h	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Wasm/WasmWebService.h	Tue Jul 10 12:39:01 2018 +0200
@@ -39,10 +39,12 @@
 
     virtual void ScheduleGetRequest(ICallback& callback,
                                     const std::string& uri,
+                                    const Headers& headers,
                                     Orthanc::IDynamicObject* payload);
 
     virtual void SchedulePostRequest(ICallback& callback,
                                      const std::string& uri,
+                                     const Headers& headers,
                                      const std::string& body,
                                      Orthanc::IDynamicObject* payload);
 
--- a/Platforms/Wasm/WasmWebService.js	Tue Jul 03 13:19:56 2018 +0200
+++ b/Platforms/Wasm/WasmWebService.js	Tue Jul 10 12:39:01 2018 +0200
@@ -1,22 +1,28 @@
 mergeInto(LibraryManager.library, {
-  WasmWebService_ScheduleGetRequest: function(callback, url, payload) {
+  WasmWebService_ScheduleGetRequest: function(callback, url, headersInJsonString, payload) {
     // Directly use XMLHttpRequest (no jQuery) to retrieve the raw binary data
     // http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/
     var xhr = new XMLHttpRequest();
-    var tmp = UTF8ToString(url);
-    xhr.open('GET', tmp, true);
+    var url_ = UTF8ToString(url);
+    var headersInJsonString_ = UTF8ToString(headersInJsonString);
+
+    xhr.open('GET', url_, true);
     xhr.responseType = 'arraybuffer';
-
+    var headers = JSON.parse(headersInJsonString_);
+    for (var key in headers) {
+      xhr.setRequestHeader(key, headers[key]);
+    }
+    // console.log(xhr); 
     xhr.onreadystatechange = function() {
       if (this.readyState == XMLHttpRequest.DONE) {
         if (xhr.status === 200) {
           // TODO - Is "new Uint8Array()" necessary? This copies the
           // answer to the WebAssembly stack, hence necessitating
           // increasing the TOTAL_STACK parameter of Emscripten
-          WasmWebService_NotifySuccess(callback, tmp, new Uint8Array(this.response),
+          WasmWebService_NotifySuccess(callback, url_, new Uint8Array(this.response),
                                        this.response.byteLength, payload);
         } else {
-          WasmWebService_NotifyError(callback, tmp, payload);
+          WasmWebService_NotifyError(callback, url_, payload);
         }
       }
     }
@@ -24,20 +30,26 @@
     xhr.send();
   },
 
-  WasmWebService_SchedulePostRequest: function(callback, url, body, bodySize, payload) {
+  WasmWebService_SchedulePostRequest: function(callback, url, headersInJsonString, body, bodySize, payload) {
     var xhr = new XMLHttpRequest();
-    var tmp = UTF8ToString(url);
-    xhr.open('POST', tmp, true);
+    var url_ = UTF8ToString(url);
+    var headersInJsonString_ = UTF8ToString(headersInJsonString);
+    xhr.open('POST', url_, true);
     xhr.responseType = 'arraybuffer';
     xhr.setRequestHeader('Content-type', 'application/octet-stream');
+
+    var headers = JSON.parse(headersInJsonString_);
+    for (var key in headers) {
+      xhr.setRequestHeader(key, headers[key]);
+    }
     
     xhr.onreadystatechange = function() {
       if (this.readyState == XMLHttpRequest.DONE) {
         if (xhr.status === 200) {
-          WasmWebService_NotifySuccess(callback, tmp, new Uint8Array(this.response),
+          WasmWebService_NotifySuccess(callback, url_, new Uint8Array(this.response),
                                        this.response.byteLength, payload);
         } else {
-          WasmWebService_NotifyError(callback, tmp, payload);
+          WasmWebService_NotifyError(callback, url_, payload);
         }
       }
     }
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue Jul 03 13:19:56 2018 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Tue Jul 10 12:39:01 2018 +0200
@@ -55,7 +55,7 @@
 
       for (size_t i = 0; i < loader.GetSliceCount(); i++)
       {
-        const_cast<OrthancSlicesLoader&>(loader).ScheduleLoadSliceImage(i, SliceImageQuality_Full);
+        const_cast<OrthancSlicesLoader&>(loader).ScheduleLoadSliceImage(i, SliceImageQuality_FullPng);
       }
     }