changeset 154:32a94bbb7d05

merge
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 08 Oct 2018 12:12:39 +0200
parents b798d200ac90 (current diff) a0a3530ff69c (diff)
children 1304498491e4
files Framework/Algorithms/PyramidReader.cpp Framework/Algorithms/PyramidReader.h Framework/Algorithms/ReconstructPyramidCommand.cpp Framework/Algorithms/TranscodeTileCommand.cpp Framework/ImageToolbox.cpp Framework/Inputs/SingleLevelDecodedPyramid.cpp Framework/Inputs/SingleLevelDecodedPyramid.h Framework/Jpeg2000Reader.h Framework/MultiThreading/BagOfTasks.h Framework/MultiThreading/ICommand.h Framework/Outputs/InMemoryTiledImage.cpp ViewerPlugin/CMakeLists.txt ViewerPlugin/Plugin.cpp
diffstat 23 files changed, 689 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/ApplicationToolbox.cpp	Fri Jul 27 14:06:26 2018 +0200
+++ b/Applications/ApplicationToolbox.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -22,13 +22,14 @@
 #include "ApplicationToolbox.h"
 
 #include "../Framework/Inputs/OpenSlideLibrary.h"
+#include "../Framework/MultiThreading/BagOfTasksProcessor.h"
 
 #include <Core/DicomParsing/FromDcmtkBridge.h>
 #include <Core/HttpClient.h>
 #include <Core/Logging.h>
-#include <Core/MultiThreading/BagOfTasksProcessor.h>
 #include <Core/OrthancException.h>
 #include <Core/SystemToolbox.h>
+#include <Core/Toolbox.h>
 
 #include <boost/filesystem.hpp>
 #include <boost/lexical_cast.hpp>
@@ -63,7 +64,7 @@
     void GlobalInitialize()
     {
       Orthanc::Logging::Initialize();
-      Orthanc::HttpClient::InitializeOpenSsl();
+      Orthanc::Toolbox::InitializeOpenSsl();
       Orthanc::HttpClient::GlobalInitialize();
       Orthanc::FromDcmtkBridge::InitializeDictionary(false /* don't load private dictionary */);
       assert(DisplayPerformanceWarning());
@@ -74,7 +75,7 @@
     {
       OrthancWSI::OpenSlideLibrary::Finalize();
       Orthanc::HttpClient::GlobalFinalize();
-      Orthanc::HttpClient::FinalizeOpenSsl();
+      Orthanc::Toolbox::FinalizeOpenSsl();
     }
 
 
@@ -279,8 +280,8 @@
       if (options.count(OPTION_USERNAME) &&
           options.count(OPTION_PASSWORD))
       {
-        parameters.SetUsername(options[OPTION_USERNAME].as<std::string>());
-        parameters.SetPassword(options[OPTION_PASSWORD].as<std::string>());
+        parameters.SetCredentials(options[OPTION_USERNAME].as<std::string>(),
+                                  options[OPTION_PASSWORD].as<std::string>());
       }
 
       if (options.count(OPTION_TIMEOUT))
--- a/Applications/ApplicationToolbox.h	Fri Jul 27 14:06:26 2018 +0200
+++ b/Applications/ApplicationToolbox.h	Mon Oct 08 12:12:39 2018 +0200
@@ -21,7 +21,8 @@
 
 #pragma once
 
-#include <Core/MultiThreading/BagOfTasks.h>
+#include "../Framework/MultiThreading/BagOfTasks.h"
+
 #include <Core/WebServiceParameters.h>
 
 #include <string>
--- a/Applications/CMakeLists.txt	Fri Jul 27 14:06:26 2018 +0200
+++ b/Applications/CMakeLists.txt	Mon Oct 08 12:12:39 2018 +0200
@@ -100,14 +100,15 @@
   ${ORTHANC_WSI_DIR}/Framework/Inputs/TiledPyramidStatistics.cpp
   ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Reader.cpp
   ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Writer.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Targets/FolderTarget.cpp
-  ${ORTHANC_WSI_DIR}/Framework/Targets/OrthancTarget.cpp
+  ${ORTHANC_WSI_DIR}/Framework/MultiThreading/BagOfTasksProcessor.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/DicomPyramidWriter.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/HierarchicalTiffWriter.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/InMemoryTiledImage.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/MultiframeDicomWriter.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/PyramidWriterBase.cpp
   ${ORTHANC_WSI_DIR}/Framework/Outputs/TruncatedPyramidWriter.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Targets/FolderTarget.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Targets/OrthancTarget.cpp
   )
 
 EmbedResources(
--- a/Applications/Dicomizer.cpp	Fri Jul 27 14:06:26 2018 +0200
+++ b/Applications/Dicomizer.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -29,12 +29,12 @@
 #include "../Framework/Inputs/TiledJpegImage.h"
 #include "../Framework/Inputs/TiledPngImage.h"
 #include "../Framework/Inputs/TiledPyramidStatistics.h"
+#include "../Framework/MultiThreading/BagOfTasksProcessor.h"
 #include "../Framework/Outputs/DicomPyramidWriter.h"
 #include "../Framework/Outputs/TruncatedPyramidWriter.h"
 
 #include <Core/DicomParsing/FromDcmtkBridge.h>
 #include <Core/Logging.h>
-#include <Core/MultiThreading/BagOfTasksProcessor.h>
 #include <Core/OrthancException.h>
 #include <Core/SystemToolbox.h>
 
--- a/Framework/Algorithms/PyramidReader.cpp	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/Algorithms/PyramidReader.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -61,9 +61,10 @@
         assert(tileY_ * that_.sourceTileHeight_ < that_.levelHeight_);
 
         unsigned int bottom = that_.levelHeight_ - tileY_ * that_.sourceTileHeight_;
-        Orthanc::ImageAccessor a = decoded_->GetRegion(0, bottom, 
-                                                       that_.sourceTileWidth_, 
-                                                       that_.sourceTileHeight_ - bottom);
+        Orthanc::ImageAccessor a;
+        decoded_->GetRegion(a, 0, bottom,
+                            that_.sourceTileWidth_, 
+                            that_.sourceTileHeight_ - bottom);
         ImageToolbox::Set(a, 
                           that_.parameters_.GetBackgroundColorRed(),
                           that_.parameters_.GetBackgroundColorGreen(),
@@ -77,9 +78,10 @@
         assert(tileX_ * that_.sourceTileWidth_ < that_.levelWidth_);
 
         unsigned int right = that_.levelWidth_ - tileX_ * that_.sourceTileWidth_;
-        Orthanc::ImageAccessor a = decoded_->GetRegion(right, 0, 
-                                                       that_.sourceTileWidth_ - right, 
-                                                       that_.sourceTileHeight_);
+        Orthanc::ImageAccessor a;
+        decoded_->GetRegion(a, right, 0, 
+                            that_.sourceTileWidth_ - right, 
+                            that_.sourceTileHeight_);
         ImageToolbox::Set(a,
                           that_.parameters_.GetBackgroundColorRed(),
                           that_.parameters_.GetBackgroundColorGreen(),
@@ -283,37 +285,37 @@
   }
 
 
-  Orthanc::ImageAccessor PyramidReader::GetDecodedTile(unsigned int tileX,
-                                                       unsigned int tileY)
+  void PyramidReader::GetDecodedTile(Orthanc::ImageAccessor& target,
+                                     unsigned int tileX,
+                                     unsigned int tileY)
   {
     if (tileX * targetTileWidth_ >= levelWidth_ ||
         tileY * targetTileHeight_ >= levelHeight_)
     {
       // Accessing a tile out of the source image
-      return GetOutsideTile();
+      GetOutsideTile().GetReadOnlyAccessor(target);
     }
+    else
+    {
+      SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY));
+      const Orthanc::ImageAccessor& tile = source.GetDecodedTile();
 
-    SourceTile& source = AccessSourceTile(MapTargetToSourceLocation(tileX, tileY));
-    const Orthanc::ImageAccessor& tile = source.GetDecodedTile();
+      CheckTileSize(tile);
 
-    CheckTileSize(tile);
-
-    assert(sourceTileWidth_ % targetTileWidth_ == 0 &&
-           sourceTileHeight_ % targetTileHeight_ == 0);
+      assert(sourceTileWidth_ % targetTileWidth_ == 0 &&
+             sourceTileHeight_ % targetTileHeight_ == 0);
 
-    unsigned int xx = tileX % (sourceTileWidth_ / targetTileWidth_);
-    unsigned int yy = tileY % (sourceTileHeight_ / targetTileHeight_);
-
-    const uint8_t* bytes = 
-      reinterpret_cast<const uint8_t*>(tile.GetConstRow(yy * targetTileHeight_)) +
-      GetBytesPerPixel(tile.GetFormat()) * xx * targetTileWidth_;
+      unsigned int xx = tileX % (sourceTileWidth_ / targetTileWidth_);
+      unsigned int yy = tileY % (sourceTileHeight_ / targetTileHeight_);
 
-    Orthanc::ImageAccessor region;
-    region.AssignReadOnly(tile.GetFormat(),
-                          targetTileWidth_,
-                          targetTileHeight_,
-                          tile.GetPitch(), bytes);                                    
+      const uint8_t* bytes = 
+        reinterpret_cast<const uint8_t*>(tile.GetConstRow(yy * targetTileHeight_)) +
+        GetBytesPerPixel(tile.GetFormat()) * xx * targetTileWidth_;
 
-    return region;
+      target.AssignReadOnly(tile.GetFormat(),
+                            targetTileWidth_,
+                            targetTileHeight_,
+                            tile.GetPitch(), bytes);                                    
+    }
   }
 }
--- a/Framework/Algorithms/PyramidReader.h	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/Algorithms/PyramidReader.h	Mon Oct 08 12:12:39 2018 +0200
@@ -88,7 +88,8 @@
                                   unsigned int tileX,
                                   unsigned int tileY);
 
-    Orthanc::ImageAccessor GetDecodedTile(unsigned int tileX,
-                                          unsigned int tileY);  
+    void GetDecodedTile(Orthanc::ImageAccessor& target,
+                        unsigned int tileX,
+                        unsigned int tileY);  
   };
 }
--- a/Framework/Algorithms/ReconstructPyramidCommand.cpp	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/Algorithms/ReconstructPyramidCommand.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -50,7 +50,8 @@
 
     if (level == 0)
     {
-      result.reset(new Orthanc::ImageAccessor(source_.GetDecodedTile(x, y)));
+      result.reset(new Orthanc::ImageAccessor);
+      source_.GetDecodedTile(*result, x, y);
 
       ImageCompression compression;
       const std::string* rawTile = source_.GetRawTile(compression, x, y);
--- a/Framework/Algorithms/ReconstructPyramidCommand.h	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/Algorithms/ReconstructPyramidCommand.h	Mon Oct 08 12:12:39 2018 +0200
@@ -23,7 +23,7 @@
 
 #include "PyramidReader.h"
 #include "../Outputs/IPyramidWriter.h"
-#include <Core/MultiThreading/BagOfTasks.h>
+#include "../MultiThreading/BagOfTasks.h"
 
 
 namespace OrthancWSI
--- a/Framework/Algorithms/TranscodeTileCommand.cpp	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/Algorithms/TranscodeTileCommand.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -73,7 +73,8 @@
         }
         else
         {
-          Orthanc::ImageAccessor tile = source_.GetDecodedTile(x, y);
+          Orthanc::ImageAccessor tile;
+          source_.GetDecodedTile(tile, x, y);
 
           // Re-encoding the file
           target_.EncodeTile(tile, level_, x, y);
--- a/Framework/Algorithms/TranscodeTileCommand.h	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/Algorithms/TranscodeTileCommand.h	Mon Oct 08 12:12:39 2018 +0200
@@ -23,8 +23,7 @@
 
 #include "PyramidReader.h"
 #include "../Outputs/IPyramidWriter.h"
-
-#include <Core/MultiThreading/BagOfTasks.h>
+#include "../MultiThreading/BagOfTasks.h"
 
 namespace OrthancWSI
 {
--- a/Framework/ImageToolbox.cpp	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/ImageToolbox.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -68,8 +68,10 @@
       unsigned int h = std::min(source.GetHeight(), target.GetHeight() - y);
       unsigned int w = std::min(source.GetWidth(), target.GetWidth() - x);
 
-      Orthanc::ImageAccessor targetRegion = target.GetRegion(x, y, w, h);
-      Orthanc::ImageAccessor sourceRegion = source.GetRegion(0, 0, w, h);
+      Orthanc::ImageAccessor targetRegion, sourceRegion;
+      target.GetRegion(targetRegion, x, y, w, h);
+      source.GetRegion(sourceRegion, 0, 0, w, h);
+      
       Orthanc::ImageProcessing::Copy(targetRegion, sourceRegion);
     }
 
--- a/Framework/Inputs/SingleLevelDecodedPyramid.cpp	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/Inputs/SingleLevelDecodedPyramid.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -32,7 +32,8 @@
                                              unsigned int x,
                                              unsigned int y)
   {
-    Orthanc::ImageAccessor region = image_.GetRegion(x, y, target.GetWidth(), target.GetHeight());
+    Orthanc::ImageAccessor region;
+    image_.GetRegion(region, x, y, target.GetWidth(), target.GetHeight());
     ImageToolbox::Copy(target, region);
   }
 
--- a/Framework/Inputs/SingleLevelDecodedPyramid.h	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/Inputs/SingleLevelDecodedPyramid.h	Mon Oct 08 12:12:39 2018 +0200
@@ -35,7 +35,7 @@
   protected:
     void SetImage(const Orthanc::ImageAccessor& image)
     {
-      image_ = image;
+      image.GetReadOnlyAccessor(image_);
     }
 
     virtual void ReadRegion(Orthanc::ImageAccessor& target,
--- a/Framework/Jpeg2000Reader.h	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/Jpeg2000Reader.h	Mon Oct 08 12:12:39 2018 +0200
@@ -33,9 +33,7 @@
     Jpeg2000Format_Unknown
   };
 
-  class Jpeg2000Reader : 
-    public Orthanc::ImageAccessor,
-    public boost::noncopyable
+  class Jpeg2000Reader : public Orthanc::ImageAccessor
   {
   private:
     std::auto_ptr<Orthanc::ImageAccessor> image_;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MultiThreading/BagOfTasks.h	Mon Oct 08 12:12:39 2018 +0200
@@ -0,0 +1,72 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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 "ICommand.h"
+
+#include <list>
+#include <cstddef>
+
+namespace Orthanc
+{
+  class BagOfTasks : public boost::noncopyable
+  {
+  private:
+    typedef std::list<ICommand*>  Tasks;
+
+    Tasks  tasks_;
+
+  public:
+    ~BagOfTasks()
+    {
+      for (Tasks::iterator it = tasks_.begin(); it != tasks_.end(); ++it)
+      {
+        delete *it;
+      }
+    }
+
+    ICommand* Pop()
+    {
+      ICommand* task = tasks_.front();
+      tasks_.pop_front();
+      return task;
+    }
+
+    void Push(ICommand* task)   // Takes ownership
+    {
+      if (task != NULL)
+      {
+        tasks_.push_back(task);
+      }
+    }
+
+    size_t GetSize() const
+    {
+      return tasks_.size();
+    }
+
+    bool IsEmpty() const
+    {
+      return tasks_.empty();
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MultiThreading/BagOfTasksProcessor.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -0,0 +1,264 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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 "BagOfTasksProcessor.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+
+#include <stdio.h>
+
+namespace Orthanc
+{
+  class BagOfTasksProcessor::Task : public IDynamicObject
+  {
+  private:
+    uint64_t                 bag_;
+    std::auto_ptr<ICommand>  command_;
+
+  public:
+    Task(uint64_t  bag,
+         ICommand* command) :
+      bag_(bag),
+      command_(command)
+    {
+    }
+
+    bool Execute()
+    {
+      try
+      {
+        return command_->Execute();
+      }
+      catch (OrthancException& e)
+      {
+        LOG(ERROR) << "Exception while processing a bag of tasks: " << e.What();
+        return false;
+      }
+      catch (std::runtime_error& e)
+      {
+        LOG(ERROR) << "Runtime exception while processing a bag of tasks: " << e.what();
+        return false;
+      }
+      catch (...)
+      {
+        LOG(ERROR) << "Native exception while processing a bag of tasks";
+        return false;
+      }
+    }
+
+    uint64_t GetBag()
+    {
+      return bag_;
+    }
+  };
+
+
+  void BagOfTasksProcessor::SignalProgress(Task& task,
+                                           Bag& bag)
+  {
+    assert(bag.done_ < bag.size_);
+
+    bag.done_ += 1;
+
+    if (bag.done_ == bag.size_)
+    {
+      exitStatus_[task.GetBag()] = (bag.status_ == BagStatus_Running);
+      bagFinished_.notify_all();
+    }
+  }
+
+  void BagOfTasksProcessor::Worker(BagOfTasksProcessor* that)
+  {
+    while (that->continue_)
+    {
+      std::auto_ptr<IDynamicObject> obj(that->queue_.Dequeue(100));
+      if (obj.get() != NULL)
+      {
+        Task& task = *dynamic_cast<Task*>(obj.get());
+
+        {
+          boost::mutex::scoped_lock lock(that->mutex_);
+
+          Bags::iterator bag = that->bags_.find(task.GetBag());
+          assert(bag != that->bags_.end());
+          assert(bag->second.done_ < bag->second.size_);
+
+          if (bag->second.status_ != BagStatus_Running)
+          {
+            // Do not execute this task, as its parent bag of tasks
+            // has failed or is tagged as canceled
+            that->SignalProgress(task, bag->second);
+            continue;
+          }
+        }
+
+        bool success = task.Execute();
+
+        {
+          boost::mutex::scoped_lock lock(that->mutex_);
+
+          Bags::iterator bag = that->bags_.find(task.GetBag());
+          assert(bag != that->bags_.end());
+
+          if (!success)
+          {
+            bag->second.status_ = BagStatus_Failed;
+          }
+
+          that->SignalProgress(task, bag->second);
+        }
+      }
+    }
+  }
+
+
+  void BagOfTasksProcessor::Cancel(int64_t bag)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    Bags::iterator it = bags_.find(bag);
+    if (it != bags_.end())
+    {
+      it->second.status_ = BagStatus_Canceled;
+    }
+  }
+
+
+  bool BagOfTasksProcessor::Join(int64_t bag)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    while (continue_)
+    {
+      ExitStatus::iterator it = exitStatus_.find(bag);
+      if (it == exitStatus_.end())  // The bag is still running
+      {
+        bagFinished_.wait(lock);
+      }
+      else
+      {
+        bool status = it->second;
+        exitStatus_.erase(it);
+        return status;
+      }
+    }
+
+    return false;   // The processor is stopping
+  }
+
+
+  float BagOfTasksProcessor::GetProgress(int64_t bag)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    Bags::const_iterator it = bags_.find(bag);
+    if (it == bags_.end())
+    {
+      // The bag of tasks has finished
+      return 1.0f;
+    }
+    else
+    {
+      return (static_cast<float>(it->second.done_) / 
+              static_cast<float>(it->second.size_));
+    }
+  }
+
+
+  bool BagOfTasksProcessor::Handle::Join()
+  {
+    if (hasJoined_)
+    {
+      return status_;
+    }
+    else
+    {
+      status_ = that_.Join(bag_);
+      hasJoined_ = true;
+      return status_;
+    }
+  }
+
+
+  BagOfTasksProcessor::BagOfTasksProcessor(size_t countThreads) : 
+    countBags_(0),
+    continue_(true)
+  {
+    if (countThreads == 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    threads_.resize(countThreads);
+
+    for (size_t i = 0; i < threads_.size(); i++)
+    {
+      threads_[i] = new boost::thread(Worker, this);
+    }
+  }
+
+
+  BagOfTasksProcessor::~BagOfTasksProcessor()
+  {
+    continue_ = false;
+
+    bagFinished_.notify_all();   // Wakes up all the pending "Join()"
+
+    for (size_t i = 0; i < threads_.size(); i++)
+    {
+      if (threads_[i])
+      {
+        if (threads_[i]->joinable())
+        {
+          threads_[i]->join();
+        }
+
+        delete threads_[i];
+        threads_[i] = NULL;
+      }
+    }
+  }
+
+
+  BagOfTasksProcessor::Handle* BagOfTasksProcessor::Submit(BagOfTasks& tasks)
+  {
+    if (tasks.GetSize() == 0)
+    {
+      return new Handle(*this, 0, true);
+    }
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    uint64_t id = countBags_;
+    countBags_ += 1;
+
+    Bag bag(tasks.GetSize());
+    bags_[id] = bag;
+
+    while (!tasks.IsEmpty())
+    {
+      queue_.Enqueue(new Task(id, tasks.Pop()));
+    }
+
+    return new Handle(*this, id, false);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MultiThreading/BagOfTasksProcessor.h	Mon Oct 08 12:12:39 2018 +0200
@@ -0,0 +1,139 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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 "BagOfTasks.h"
+
+#include <Core/MultiThreading/SharedMessageQueue.h>
+
+#include <stdint.h>
+#include <map>
+
+namespace Orthanc
+{
+  class BagOfTasksProcessor : public boost::noncopyable
+  {
+  private:
+    enum BagStatus
+    {
+      BagStatus_Running,
+      BagStatus_Canceled,
+      BagStatus_Failed
+    };
+
+
+    struct Bag
+    {
+      size_t    size_;
+      size_t    done_;
+      BagStatus status_;
+
+      Bag() :
+        size_(0),
+        done_(0),
+        status_(BagStatus_Failed)
+      {
+      }
+
+      explicit Bag(size_t size) : 
+        size_(size),
+        done_(0),
+        status_(BagStatus_Running)
+      {
+      }
+    };
+
+    class Task;
+
+
+    typedef std::map<uint64_t, Bag>   Bags;
+    typedef std::map<uint64_t, bool>  ExitStatus;
+
+    SharedMessageQueue  queue_;
+
+    boost::mutex  mutex_;
+    uint64_t  countBags_;
+    Bags bags_;
+    std::vector<boost::thread*>   threads_;
+    ExitStatus  exitStatus_;
+    bool continue_;
+
+    boost::condition_variable  bagFinished_;
+
+    static void Worker(BagOfTasksProcessor* that);
+
+    void Cancel(int64_t bag);
+
+    bool Join(int64_t bag);
+
+    float GetProgress(int64_t bag);
+
+    void SignalProgress(Task& task,
+                        Bag& bag);
+
+  public:
+    class Handle : public boost::noncopyable
+    {
+      friend class BagOfTasksProcessor;
+
+    private:
+      BagOfTasksProcessor&  that_;
+      uint64_t              bag_;
+      bool                  hasJoined_;
+      bool                  status_;
+ 
+      Handle(BagOfTasksProcessor&  that,
+             uint64_t bag,
+             bool empty) : 
+        that_(that),
+        bag_(bag),
+        hasJoined_(empty)
+      {
+      }
+
+    public:
+      ~Handle()
+      {
+        Join();
+      }
+
+      void Cancel()
+      {
+        that_.Cancel(bag_);
+      }
+
+      bool Join();
+
+      float GetProgress()
+      {
+        return that_.GetProgress(bag_);
+      }
+    };
+  
+
+    explicit BagOfTasksProcessor(size_t countThreads);
+
+    ~BagOfTasksProcessor();
+
+    Handle* Submit(BagOfTasks& tasks);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MultiThreading/ICommand.h	Mon Oct 08 12:12:39 2018 +0200
@@ -0,0 +1,37 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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 <Core/IDynamicObject.h>
+
+namespace Orthanc
+{
+  /**
+   * This class is the base class for the "Command" design pattern.
+   * http://en.wikipedia.org/wiki/Command_pattern
+   **/
+  class ICommand : public IDynamicObject
+  {
+  public:
+    virtual bool Execute() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MultiThreading/Semaphore.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -0,0 +1,50 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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 "Semaphore.h"
+
+
+namespace OrthancWSI
+{
+  Semaphore::Semaphore(unsigned int count) : count_(count)
+  {
+  }
+
+  void Semaphore::Release()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    count_++;
+    condition_.notify_one(); 
+  }
+
+  void Semaphore::Acquire()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    while (count_ == 0)
+    {
+      condition_.wait(lock);
+    }
+
+    count_++;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/MultiThreading/Semaphore.h	Mon Oct 08 12:12:39 2018 +0200
@@ -0,0 +1,61 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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 <boost/noncopyable.hpp>
+#include <boost/thread.hpp>
+
+namespace OrthancWSI
+{
+  class Semaphore : public boost::noncopyable
+  {
+  private:
+    unsigned int count_;
+    boost::mutex mutex_;
+    boost::condition_variable condition_;
+
+  public:
+    explicit Semaphore(unsigned int count);
+
+    void Release();
+
+    void Acquire();
+
+    class Locker : public boost::noncopyable
+    {
+    private:
+      Semaphore&  that_;
+
+    public:
+      explicit Locker(Semaphore& that) :
+        that_(that)
+      {
+        that_.Acquire();
+      }
+
+      ~Locker()
+      {
+        that_.Release();
+      }
+    };
+  };
+}
--- a/Framework/Outputs/InMemoryTiledImage.cpp	Fri Jul 27 14:06:26 2018 +0200
+++ b/Framework/Outputs/InMemoryTiledImage.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -117,7 +117,9 @@
       Tiles::const_iterator it = tiles_.find(std::make_pair(tileX, tileY));
       if (it != tiles_.end())
       {
-        return new Orthanc::ImageAccessor(*it->second);
+        std::auto_ptr<Orthanc::ImageAccessor> result(new Orthanc::ImageAccessor);
+        it->second->GetReadOnlyAccessor(*result);
+        return result.release();
       }
       else
       {
--- a/ViewerPlugin/CMakeLists.txt	Fri Jul 27 14:06:26 2018 +0200
+++ b/ViewerPlugin/CMakeLists.txt	Mon Oct 08 12:12:39 2018 +0200
@@ -154,6 +154,7 @@
   ${ORTHANC_WSI_DIR}/Framework/Inputs/PyramidWithRawTiles.cpp
   ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Reader.cpp
   ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Writer.cpp
+  ${ORTHANC_WSI_DIR}/Framework/MultiThreading/Semaphore.cpp
 
   ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp
   ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp
--- a/ViewerPlugin/Plugin.cpp	Fri Jul 27 14:06:26 2018 +0200
+++ b/ViewerPlugin/Plugin.cpp	Mon Oct 08 12:12:39 2018 +0200
@@ -23,7 +23,9 @@
 
 #include "DicomPyramidCache.h"
 #include "../Framework/Jpeg2000Reader.h"
+#include "../Framework/MultiThreading/Semaphore.h"
 
+#include <Core/Logging.h>
 #include <Core/Images/ImageProcessing.h>
 #include <Core/Images/PngWriter.h>
 #include <Core/MultiThreading/Semaphore.h>
@@ -310,6 +312,8 @@
       return -1;
     }
 
+    Orthanc::Logging::Initialize(context);
+
     // Limit the number of PNG transcoders to the number of available
     // hardware threads (e.g. number of CPUs or cores or
     // hyperthreading units)