changeset 2295:bc68bb942f9e refactoring tip

added class StoneApplication::Configuration
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 25 Sep 2026 18:24:15 +0200
parents 074fcfd5306c
children
files Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp Applications/StoneWebViewer/WebApplication/app.js Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Sources/Loaders/GenericLoadersContext.cpp OrthancStone/Sources/Loaders/GenericLoadersContext.h OrthancStone/Sources/Oracle/GenericOracleRunner.cpp OrthancStone/Sources/Oracle/GenericOracleRunner.h OrthancStone/Sources/Oracle/ThreadedOracle.cpp OrthancStone/Sources/Oracle/ThreadedOracle.h OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.h OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h OrthancStone/Sources/StoneApplication.cpp OrthancStone/Sources/StoneApplication.h
diffstat 18 files changed, 557 insertions(+), 593 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -144,14 +144,6 @@
     ProcessOptions(argc, argv);
 
     /**
-    Create the shared loaders context
-    */
-    loadersContext_.reset(new GenericLoadersContext(1, 4, 1));
-
-    // we are in SDL --> downcast to concrete type
-    boost::shared_ptr<GenericLoadersContext> loadersContext = boost::dynamic_pointer_cast<GenericLoadersContext>(loadersContext_);
-
-    /**
       Url of the Orthanc instance
       Typically, in a native application (Qt, SDL), it will be an absolute URL like "http://localhost:8042". In 
       wasm on the browser, it could be an absolute URL, provided you do not have cross-origin problems, or a relative
@@ -161,6 +153,8 @@
     */
     std::string orthancUrl = arguments_["orthanc"];
 
+    StoneApplication::Configuration configuration;
+
     {
       Orthanc::WebServiceParameters p;
       if (HasArgument("orthanc"))
@@ -176,9 +170,19 @@
       {
         ORTHANC_ASSERT(!HasArgument("password"));
       }
-      loadersContext->SetOrthancParameters(p);
+      configuration.SetRemoteOrthancParameters(p);
     }
 
+    OrthancStone::StoneApplication::Initialize(configuration);
+
+    /**
+    Create the shared loaders context
+    */
+    loadersContext_.reset(new GenericLoadersContext(configuration, 1, 4, 1));
+
+    // we are in SDL --> downcast to concrete type
+    boost::shared_ptr<GenericLoadersContext> loadersContext = boost::dynamic_pointer_cast<GenericLoadersContext>(loadersContext_);
+
     loadersContext->StartOracle();
 
     CreateLoaders();
@@ -462,7 +466,6 @@
 int main(int argc, char* argv[])
 {
   OrthancStone::StoneInitialize();
-  OrthancStone::StoneApplication::Initialize();
   OrthancStone::SdlWindow::GlobalInitialize();
 
   int status = 0;
--- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -181,7 +181,6 @@
   int status = 0;
 
   OrthancStone::StoneInitialize();
-  OrthancStone::StoneApplication::Initialize();
   OrthancStone::SdlWindow::GlobalInitialize();
 
   try
@@ -204,11 +203,14 @@
       boost::shared_ptr<OrthancStone::UndoStack> undoStack(new OrthancStone::UndoStack);
 #endif
 
-      OrthancStone::GenericLoadersContext context(1, 4, 1);
-
       Orthanc::WebServiceParameters orthancWebService;
       orthancWebService.SetUrl(orthancUrl);
-      context.SetOrthancParameters(orthancWebService);
+
+      OrthancStone::StoneApplication::Configuration configuration;
+      configuration.SetRemoteOrthancParameters(orthancWebService);
+
+      OrthancStone::GenericLoadersContext context(configuration, 1, 4, 1);
+      OrthancStone::StoneApplication::Initialize(configuration);
 
       context.StartOracle();
 
--- a/Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -26,6 +26,7 @@
 // Stone of Orthanc includes
 #include "../../../../OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.h"
 #include "../../../../OrthancStone/Sources/Platforms/WebAssembly/WebGLViewport.h"
+#include "../../../../OrthancStone/Sources/StoneApplication.h"
 #include "../../../../OrthancStone/Sources/StoneException.h"
 #include "../../../../OrthancStone/Sources/StoneInitialization.h"
 
@@ -141,19 +142,26 @@
 
   void RtViewerApp::RunWasm()
   {
-    loadersContext_.reset(new WebAssemblyLoadersContext(1, 4, 1));
+    StoneApplication::Configuration configuration;
+
+    if (HasArgument("orthanc"))
+    {
+      configuration.SetLocalOrthancRoot(GetArgument("orthanc"));
+    }
+    else
+    {
+      configuration.SetLocalOrthancRoot("..");
+    }
+
+    configuration.SetDicomCacheSize(128 * 1024 * 1024);  // 128MB
+
+    OrthancStone::StoneApplication::Initialize(configuration);
+    loadersContext_.reset(new WebAssemblyLoadersContext(configuration, 1, 4, 1));
 
     // we are in WASM --> downcast to concrete type
     boost::shared_ptr<WebAssemblyLoadersContext> loadersContext = 
       boost::dynamic_pointer_cast<WebAssemblyLoadersContext>(loadersContext_);
 
-    if (HasArgument("orthanc"))
-      loadersContext->SetLocalOrthanc(GetArgument("orthanc"));
-    else 
-      loadersContext->SetLocalOrthanc("..");
-
-    loadersContext->SetDicomCacheSize(128 * 1024 * 1024);  // 128MB
-
     CreateLoaders();
     
     CreateView("RtViewer_Axial", VolumeProjection_Axial);
@@ -178,7 +186,6 @@
     try
     {
       OrthancStone::StoneInitialize();
-      OrthancStone::StoneApplication::Initialize();
 
       //Orthanc::Logging::EnableTraceLevel(true);
       Orthanc::Logging::EnableInfoLevel(true);
--- a/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -25,6 +25,7 @@
 #include "../../../../OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.h"
 #include "../../../../OrthancStone/Sources/Platforms/WebAssembly/WebGLViewport.h"
 #include "../../../../OrthancStone/Sources/Platforms/WebAssembly/WebGLViewportsRegistry.h"
+#include "../../../../OrthancStone/Sources/StoneApplication.h"
 #include "../../../../OrthancStone/Sources/StoneException.h"
 #include "../../../../OrthancStone/Sources/StoneInitialization.h"
 
@@ -80,7 +81,6 @@
     try
     {
       OrthancStone::StoneInitialize();
-      OrthancStone::StoneApplication::Initialize();
 
       Orthanc::Logging::EnableInfoLevel(true);
       //Orthanc::Logging::EnableTraceLevel(true);
@@ -91,9 +91,13 @@
                    << "." << __EMSCRIPTEN_tiny__;
 
       LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness());
-      context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1));
-      context_->SetLocalOrthanc("..");
-      context_->SetDicomCacheSize(128 * 1024 * 1024);  // 128MB
+
+      OrthancStone::StoneApplication::Configuration configuration;
+      configuration.SetLocalOrthancRoot("..");
+      configuration.SetDicomCacheSize(128 * 1024 * 1024);  // 128MB
+
+      OrthancStone::StoneApplication::Initialize(configuration);
+      context_.reset(new OrthancStone::WebAssemblyLoadersContext(configuration, 1, 4, 1));
   
       DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized");
     }
--- a/Applications/StoneWebViewer/WebApplication/app.js	Fri Sep 25 14:02:54 2026 +0200
+++ b/Applications/StoneWebViewer/WebApplication/app.js	Fri Sep 25 18:24:15 2026 +0200
@@ -1595,21 +1595,11 @@
    **/
 
   stone.Setup(Module);
-  stone.SetDicomWebRoot(app.globalConfiguration.DicomWebRoot,
-                        true /* assume "/rendered" is available in DICOMweb (could be a configuration option) */);
+  stone.Configure(JSON.stringify(app.globalConfiguration));
+
   stone.SetSoftwareRendering(app.settingSoftwareRendering);
   stone.SetLinearInterpolation(app.settingLinearInterpolation);
 
-  if ('DicomCacheSize' in app.globalConfiguration) {
-    stone.SetDicomCacheSize(app.globalConfiguration.DicomCacheSize);
-  }
-
-  // Calls to "stone.AddHttpHeader()" must be after "stone.SetDicomWebRoot()",
-  // and before "stone.SetSkipSeriesFromModalities()"
-  for (var header in app.globalConfiguration.DicomWebHttpHeaders) {
-    stone.AddHttpHeader(header, app.globalConfiguration.DicomWebHttpHeaders[header]);
-  }
-  
   // Bearer token is new in Stone Web viewer 2.0
   var token = getParameterFromUrl('token');
   if (token !== undefined) {
@@ -1632,10 +1622,6 @@
    * Calls to "stone.XXX()" can be reordered after this point.
    **/
   
-  if ('SkipSeriesFromModalities' in app.globalConfiguration) {
-    stone.SetSkipSeriesFromModalities(JSON.stringify(app.globalConfiguration.SkipSeriesFromModalities));
-  }
-  
   if (app.globalConfiguration.ShowInfoPanelAtStartup == 'Always') {
     app.modalNotDiagnostic = true;
   } else if (app.globalConfiguration.ShowInfoPanelAtStartup == 'Never') {
@@ -1646,16 +1632,6 @@
     alert('Bad value for option "ShowInfoPanelAtStartup": ' + app.globalConfiguration.ShowInfoPanelAtStartup);
   }
 
-  var color = app.globalConfiguration['AnnotationsColor'];
-  if (color !== undefined) {
-    stone.SetAnnotationsColor(color[0], color[1], color[2]);
-  }
-
-  color = app.globalConfiguration['HighlightedAnnotationsColor'];
-  if (color !== undefined) {
-    stone.SetHighlightedAnnotationsColor(color[0], color[1], color[2]);
-  }
-
   console.warn('Stone properly initialized');
 
   app.stoneWebViewerVersion = stone.GetStoneWebViewerVersion();
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -26,15 +26,6 @@
 #include <emscripten.h>
 
 
-// Orthanc framework includes
-#include <Cache/MemoryObjectCache.h>
-#include <DicomFormat/DicomArray.h>
-#include <DicomParsing/ParsedDicomFile.h>
-#include <Images/Image.h>
-#include <Images/ImageProcessing.h>
-#include <Images/JpegReader.h>
-#include <Logging.h>
-
 // Stone includes
 #include "../../../OrthancStone/Sources/Loaders/DicomResourcesLoader.h"
 #include "../../../OrthancStone/Sources/Loaders/SeriesMetadataLoader.h"
@@ -66,6 +57,16 @@
 #include "../../../OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.h"
 #include "../../../OrthancStone/Sources/Platforms/WebAssembly/WebGLViewport.h"
 
+// Orthanc framework includes
+#include <Cache/MemoryObjectCache.h>
+#include <DicomFormat/DicomArray.h>
+#include <DicomParsing/ParsedDicomFile.h>
+#include <Images/Image.h>
+#include <Images/ImageProcessing.h>
+#include <Images/JpegReader.h>
+#include <Logging.h>
+#include <SerializationToolbox.h>
+
 
 #include <algorithm>
 #include <boost/make_shared.hpp>
@@ -4898,6 +4899,44 @@
 // END TODO Refactoring
 
 
+static bool ParseColor(uint8_t& red,
+                       uint8_t& green,
+                       uint8_t& blue,
+                       const Json::Value& configuration,
+                       const std::string& key)
+{
+  if (configuration.isMember(key))
+  {
+    const Json::Value& value = configuration[key];
+
+    if (value.isArray() &&
+        value.size() == 3 &&
+        value[0].isIntegral() &&
+        value[1].isIntegral() &&
+        value[2].isIntegral() &&
+        value[0].asInt() >= 0 &&
+        value[0].asInt() <= 255 &&
+        value[1].asInt() >= 0 &&
+        value[1].asInt() <= 255 &&
+        value[2].asInt() >= 0 &&
+        value[2].asInt() <= 255)
+    {
+      red = static_cast<uint8_t>(value[0].asInt());
+      green = static_cast<uint8_t>(value[1].asInt());
+      blue = static_cast<uint8_t>(value[2].asInt());
+      return true;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+  }
+  else
+  {
+    return 0;
+  }
+}
+
 
 extern "C"
 {
@@ -4906,16 +4945,11 @@
     printf("Initializing Stone\n");
     OrthancStone::StoneInitialize();
 
-    OrthancStone::StoneApplication::Initialize();
-
     Orthanc::Logging::EnableInfoLevel(true);
     //Orthanc::Logging::EnableTraceLevel(true);
 
     LOG(INFO) << "Using DCMTK version: " << DCMTK_VERSION_NUMBER;
 
-    context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1));
-    context_->SetDicomCacheSize(128 * 1024 * 1024);  // 128MB
-    
     framesCache_.reset(new FramesCache);
     instancesCache_.reset(new InstancesCache);
     overlayLayerSource_.reset(new OverlayLayerSource);
@@ -4943,28 +4977,63 @@
 
 
   EMSCRIPTEN_KEEPALIVE
-  void SetDicomWebRoot(const char* uri,
-                       int useRendered)
+  void Configure(const char* globalConfiguration)
   {
     try
     {
-      source_.SetDicomWebSource(uri);
-      source_.SetDicomWebRendered(useRendered != 0);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-  
-
-  EMSCRIPTEN_KEEPALIVE
-  void SetDicomWebThroughOrthanc(const char* orthancRoot,
-                                 const char* serverName,
-                                 int hasRendered)
-  {
-    try
-    {
-      context_->SetLocalOrthanc(orthancRoot);
-      source_.SetDicomWebThroughOrthancSource(serverName);
-      source_.SetDicomWebRendered(hasRendered != 0);
+      Json::Value parsed;
+      if (Orthanc::Toolbox::ReadJson(parsed, globalConfiguration))
+      {
+        OrthancStone::StoneApplication::Configuration configuration;
+
+        source_.SetDicomWebSource(Orthanc::SerializationToolbox::ReadString(parsed, "DicomWebRoot"));
+        source_.SetDicomWebRendered(true);  // assume "/rendered" is available in DICOMweb (could be a configuration option)
+
+        // Another possibility:
+        // configuration.SetLocalOrthancRoot(orthancRoot); source_.SetDicomWebThroughOrthancSource(serverName);
+
+        unsigned int size = Orthanc::SerializationToolbox::ReadUnsignedInteger(parsed, "DicomCacheSize", 0);
+        configuration.SetDicomCacheSize(size * 1024 * 1024);  // The size is expressed in MB in the configuration file
+
+        static const char* const KEY_DICOM_WEB_HEADERS = "DicomWebHttpHeaders";
+        if (parsed.isMember(KEY_DICOM_WEB_HEADERS))
+        {
+          std::map<std::string, std::string> headers;
+          Orthanc::SerializationToolbox::ReadMapOfStrings(headers, parsed, KEY_DICOM_WEB_HEADERS);
+
+          for (std::map<std::string, std::string>::const_iterator it = headers.begin(); it != headers.end(); ++it)
+          {
+            source_.AddHttpHeader(it->first, it->second);
+          }
+        }
+
+        uint8_t r, g, b;
+        if (ParseColor(r, g, b, parsed, "AnnotationsColor"))
+        {
+          SetAnnotationsColor(OrthancStone::Color(r, g, b));
+        }
+
+        if (ParseColor(r, g, b, parsed, "HighlightedAnnotationsColor"))
+        {
+          SetHighlightedColor(OrthancStone::Color(r, g, b));
+        }
+
+        context_.reset(new OrthancStone::WebAssemblyLoadersContext(configuration, 1, 4, 1));
+
+        static const char* const KEY_SKIP_SERIES = "SkipSeriesFromModalities";
+        if (parsed.isMember(KEY_SKIP_SERIES))
+        {
+          std::vector<std::string> modalities;
+          Orthanc::SerializationToolbox::ReadArrayOfStrings(modalities, parsed, KEY_SKIP_SERIES);
+          GetResourcesLoader().SetSkipSeriesFromModalities(modalities);
+        }
+
+        OrthancStone::StoneApplication::Initialize(configuration);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+      }
     }
     EXTERN_CATCH_EXCEPTIONS;
   }
@@ -4983,50 +5052,6 @@
   
 
   EMSCRIPTEN_KEEPALIVE
-  void SetDicomCacheSize(int sizeMB)
-  {
-    try
-    {
-      if (sizeMB == 0)
-      {
-        LOG(WARNING) << "The DICOM cache is disabled";
-      }
-      else
-      {
-        LOG(INFO) << "The DICOM cache size is set to " << sizeMB << "MB";
-      }
-
-      if (sizeMB >= 0)
-      {
-        context_->SetDicomCacheSize(sizeMB * 1024 * 1024);
-      }
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-  
-
-  EMSCRIPTEN_KEEPALIVE
-  void SetSkipSeriesFromModalities(const char* value)
-  {
-    try
-    {
-      LOG(WARNING) << "SetSkipSeriesFromModalities " << value;
-      
-      Json::Value modalities;
-      Orthanc::Toolbox::ReadJson(modalities, value);
-      std::vector<std::string> skipSeriesFromModalities;
-
-      for (Json::Value::ArrayIndex i = 0; i < modalities.size(); i++)
-      {
-        skipSeriesFromModalities.push_back(modalities[i].asString());
-      }
-      GetResourcesLoader().SetSkipSeriesFromModalities(skipSeriesFromModalities);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
   void FetchAllStudies()
   {
     try
@@ -5712,34 +5737,6 @@
 
 
   EMSCRIPTEN_KEEPALIVE
-  void SetAnnotationsColor(int red,
-                           int green,
-                           int blue)
-  {
-    try
-    {
-      OrthancStone::Color color(red, green, blue);
-      SetAnnotationsColor(color);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
-  void SetHighlightedAnnotationsColor(int red,
-                                      int green,
-                                      int blue)
-  {
-    try
-    {
-      OrthancStone::Color color(red, green, blue);
-      SetHighlightedColor(color);
-    }
-    EXTERN_CATCH_EXCEPTIONS;
-  }
-
-
-  EMSCRIPTEN_KEEPALIVE
   void *Allocate(size_t size)
   {
     return malloc(size);
--- a/OrthancStone/Sources/Loaders/GenericLoadersContext.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/Loaders/GenericLoadersContext.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -93,11 +93,12 @@
   }
 
 
-  GenericLoadersContext::GenericLoadersContext(unsigned int maxHighPriority,
+  GenericLoadersContext::GenericLoadersContext(const StoneApplication::Configuration& configuration,
+                                               unsigned int maxHighPriority,
                                                unsigned int maxStandardPriority,
                                                unsigned int maxLowPriority)
   {
-    oracle_.reset(new ThreadedOracle(*this));
+    oracle_.reset(new ThreadedOracle(configuration, *this));
     scheduler_ = OracleScheduler::Create(*oracle_, oracleObservable_, *this,
                                          maxHighPriority, maxStandardPriority, maxLowPriority);
 
@@ -117,27 +118,6 @@
   }
 
   
-  void GenericLoadersContext::SetOrthancParameters(const Orthanc::WebServiceParameters& parameters)
-  {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-    oracle_->SetOrthancParameters(parameters);
-  }
-
-  
-  void GenericLoadersContext::SetRootDirectory(const std::string& root)
-  {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-    oracle_->SetRootDirectory(root);
-  }
-
-  
-  void GenericLoadersContext::SetDicomCacheSize(size_t size)
-  {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-    oracle_->SetDicomCacheSize(size);
-  }
-
-  
   void GenericLoadersContext::StartOracle()
   {
     boost::recursive_mutex::scoped_lock lock(mutex_);
--- a/OrthancStone/Sources/Loaders/GenericLoadersContext.h	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/Loaders/GenericLoadersContext.h	Fri Sep 25 18:24:15 2026 +0200
@@ -32,6 +32,7 @@
 
 namespace OrthancStone
 {
+  // TODO Refactoring - Remove this class
   class GenericLoadersContext : 
     public ILoadersContext,
     private IMessageEmitter
@@ -57,7 +58,8 @@
                              const IMessage& message) ORTHANC_OVERRIDE;
 
   public:
-    GenericLoadersContext(unsigned int maxHighPriority,
+    GenericLoadersContext(const StoneApplication::Configuration& configuration,
+                          unsigned int maxHighPriority,
                           unsigned int maxStandardPriority,
                           unsigned int maxLowPriority);
 
@@ -65,12 +67,6 @@
    
     virtual ILock* Lock() ORTHANC_OVERRIDE;
 
-    void SetOrthancParameters(const Orthanc::WebServiceParameters& parameters);
-
-    void SetRootDirectory(const std::string& root);
-    
-    void SetDicomCacheSize(size_t size);
-
     void StartOracle();
 
     void StopOracle();
--- a/OrthancStone/Sources/Oracle/GenericOracleRunner.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/Oracle/GenericOracleRunner.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -442,22 +442,22 @@
           break;
 
         case IOracleCommand::Type_OrthancRestApi:
-          RunInternal(callback, orthanc_,
+          RunInternal(callback, configuration_.GetRemoteOrthancParameters(),
                       dynamic_cast<const OrthancRestApiCommand&>(callback.GetCommand()));
           break;
 
         case IOracleCommand::Type_GetOrthancImage:
-          RunInternal(callback, orthanc_,
+          RunInternal(callback, configuration_.GetRemoteOrthancParameters(),
                       dynamic_cast<const GetOrthancImageCommand&>(callback.GetCommand()));
           break;
 
         case IOracleCommand::Type_GetOrthancWebViewerJpeg:
-          RunInternal(callback, orthanc_,
+          RunInternal(callback, configuration_.GetRemoteOrthancParameters(),
                       dynamic_cast<const GetOrthancWebViewerJpegCommand&>(callback.GetCommand()));
           break;
 
         case IOracleCommand::Type_ReadFile:
-          RunInternal(callback, rootDirectory_,
+          RunInternal(callback, configuration_.GetRootDirectory(),
                       dynamic_cast<const ReadFileCommand&>(callback.GetCommand()));
           break;
 
@@ -467,12 +467,12 @@
           switch (callback.GetCommand().GetType())
           {
             case IOracleCommand::Type_ParseDicomFromFile:
-              RunInternal(callback, dicomCache_, rootDirectory_,
+              RunInternal(callback, dicomCache_, configuration_.GetRootDirectory(),
                           dynamic_cast<const ParseDicomFromFileCommand&>(callback.GetCommand()));
               break;
 
             case IOracleCommand::Type_ParseDicomFromWado:
-              RunInternal(callback, dicomCache_, orthanc_,
+              RunInternal(callback, dicomCache_, configuration_.GetRemoteOrthancParameters(),
                           dynamic_cast<const ParseDicomFromWadoCommand&>(callback.GetCommand()));
               break;
 
--- a/OrthancStone/Sources/Oracle/GenericOracleRunner.h	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/Oracle/GenericOracleRunner.h	Fri Sep 25 18:24:15 2026 +0200
@@ -33,6 +33,7 @@
 #  include "../Toolbox/ParsedDicomCache.h"
 #endif
 
+#include "../StoneApplication.h"
 #include "IOracleCommand.h"
 #include "OracleCallback.h"
 
@@ -44,37 +45,16 @@
   class GenericOracleRunner : public boost::noncopyable
   {
   private:
-    Orthanc::WebServiceParameters  orthanc_;
-    std::string                    rootDirectory_;
+    StoneApplication::Configuration  configuration_;
 
 #if ORTHANC_ENABLE_DCMTK == 1
     boost::shared_ptr<ParsedDicomCache>  dicomCache_;
 #endif
 
   public:
-    GenericOracleRunner() :
-      rootDirectory_(".")
-    {
-    }
-
-    void SetOrthanc(const Orthanc::WebServiceParameters& orthanc)
-    {
-      orthanc_ = orthanc;
-    }
-
-    const Orthanc::WebServiceParameters& GetOrthanc() const
+    GenericOracleRunner(const StoneApplication::Configuration& configuration) :
+      configuration_(configuration)
     {
-      return orthanc_;
-    }
-
-    void SetRootDirectory(const std::string& rootDirectory)
-    {
-      rootDirectory_ = rootDirectory;
-    }
-
-    const std::string& GetRootDirectory() const
-    {
-      return rootDirectory_;
     }
 
 #if ORTHANC_ENABLE_DCMTK == 1
--- a/OrthancStone/Sources/Oracle/ThreadedOracle.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/Oracle/ThreadedOracle.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -135,20 +135,14 @@
       }
       else
       {
-        GenericOracleRunner runner;
-
-        {
-          boost::mutex::scoped_lock lock(mutex_);
-          runner.SetOrthanc(orthanc_);
-          runner.SetRootDirectory(rootDirectory_);
+        GenericOracleRunner runner(configuration_);
 
 #if ORTHANC_ENABLE_DCMTK == 1
-          if (dicomCache_)
-          {
-            runner.SetDicomCache(dicomCache_);
-          }
+        if (dicomCache_)
+        {
+          runner.SetDicomCache(dicomCache_);
+        }
 #endif
-        }
 
         runner.Run(*item);
       }
@@ -191,7 +185,7 @@
 
       that->sleepingCommands_->AwakeExpired(that->emitter_);
 
-      boost::this_thread::sleep(boost::posix_time::milliseconds(that->sleepingTimeResolution_));
+      boost::this_thread::sleep(boost::posix_time::milliseconds(that->configuration_.GetWorkersTimeResolution()));
     }
   }
 
@@ -232,14 +226,23 @@
   }
 
 
-  ThreadedOracle::ThreadedOracle(IMessageEmitter& emitter) :
+  ThreadedOracle::ThreadedOracle(const StoneApplication::Configuration& configuration,
+                                 IMessageEmitter& emitter) :
+    configuration_(configuration),
     emitter_(emitter),
-    rootDirectory_("."),
     state_(State_Setup),
     workers_(4),
-    sleepingCommands_(new SleepingCommands),
-    sleepingTimeResolution_(50)  // By default, time resolution of 50ms
+    sleepingCommands_(new SleepingCommands)
   {
+    if (configuration.GetDicomCacheSize() == 0)
+    {
+      LOG(WARNING) << "The DICOM cache is disabled";
+    }
+    else
+    {
+      LOG(INFO) << "The DICOM cache size is set to " << configuration.GetDicomCacheSize() << " bytes";
+      dicomCache_.reset(new ParsedDicomCache(configuration.GetDicomCacheSize()));
+    }
   }
 
 
@@ -266,85 +269,6 @@
   }
 
   
-  void ThreadedOracle::SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-    orthanc_ = orthanc;
-  }
-
-
-  void ThreadedOracle::SetRootDirectory(const std::string& rootDirectory)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-    rootDirectory_ = rootDirectory;
-  }
-
-
-  void ThreadedOracle::SetThreadsCount(unsigned int count)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (count == 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else if (state_ != State_Setup)
-    {
-      LOG(ERROR) << "ThreadedOracle::SetThreadsCount(): (state_ != State_Setup)";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      workers_.resize(count);
-    }
-  }
-
-
-  void ThreadedOracle::SetSleepingTimeResolution(unsigned int milliseconds)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (milliseconds == 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else if (state_ != State_Setup)
-    {
-      LOG(ERROR) << "ThreadedOracle::SetSleepingTimeResolution(): (state_ != State_Setup)";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      sleepingTimeResolution_ = milliseconds;
-    }
-  }
-
-
-  void ThreadedOracle::SetDicomCacheSize(size_t size)
-  {
-#if ORTHANC_ENABLE_DCMTK == 1
-    boost::mutex::scoped_lock lock(mutex_);
-
-    if (state_ != State_Setup)
-    {
-      LOG(ERROR) << "ThreadedOracle::SetDicomCacheSize(): (state_ != State_Setup)";
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      if (size == 0)
-      {
-        dicomCache_.reset();
-      }
-      else
-      {
-        dicomCache_.reset(new ParsedDicomCache(size));
-      }
-    }
-#endif
-  }
-
-
   void ThreadedOracle::Start()
   {
     boost::mutex::scoped_lock lock(mutex_);
@@ -483,11 +407,12 @@
     };
 
 
-    ThreadedOracle::ThreadedOracle(unsigned int countWorkers) :
-      sleepingThread_(new SleepRunnable, 50 /* milliseconds */)
+    ThreadedOracle::ThreadedOracle(const StoneApplication::Configuration& configuration) :
+      configuration_(configuration),
+      sleepingThread_(new SleepRunnable, configuration.GetWorkersTimeResolution())
     {
-      threadPool_.SetThreadsCount(countWorkers);
-      threadPool_.SetDequeueTimeout(50);
+      threadPool_.SetThreadsCount(configuration.GetOracleThreadsCount());
+      threadPool_.SetDequeueTimeout(configuration.GetWorkersTimeResolution());
     }
 
 
@@ -517,31 +442,22 @@
         throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
       }
 
-      try
+      if (command->GetType() == IOracleCommand::Type_Sleep)
       {
-        switch (command->GetType())
-        {
-          case IOracleCommand::Type_Sleep:
-          {
-            SleepRunnable& runnable = dynamic_cast<SleepRunnable&>(sleepingThread_.GetRunnable());
-            runnable.Add(environment, client, dynamic_cast<SleepOracleCommand*>(protection.release()));
-            break;
-          }
+        SleepRunnable& runnable = dynamic_cast<SleepRunnable&>(sleepingThread_.GetRunnable());
+        runnable.Add(environment, client, dynamic_cast<SleepOracleCommand*>(protection.release()));
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
 
-          default:
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
-                                            "Command type not implemented by the Threaded Oracle: " +
-                                            boost::lexical_cast<std::string>(command->GetType()));
-        }
-      }
-      catch (Orthanc::OrthancException& e)
-      {
-        environment.NotifyOracleError(client, protection.release(), e);
-      }
-      catch (...)
-      {
-        environment.NotifyOracleError(client, protection.release(), Orthanc::OrthancException(Orthanc::ErrorCode_InternalError));
-      }
+
+    bool ThreadedOracle::Schedule(boost::shared_ptr<IObserver> receiver,
+                                  IOracleCommand* command)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
   }
 }
--- a/OrthancStone/Sources/Oracle/ThreadedOracle.h	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/Oracle/ThreadedOracle.h	Fri Sep 25 18:24:15 2026 +0200
@@ -44,8 +44,8 @@
 
 #include "../Messages/IMessageEmitter.h"
 #include "../Platforms/Native/RunnableThread.h"
+#include "../StoneApplication.h"
 #include "GenericOracleRunner.h"
-#include "IEnvironment.h"
 #include "IOracle.h"
 
 #include <MultiThreading/SharedMessageQueue.h>
@@ -66,16 +66,14 @@
 
     class SleepingCommands;
 
+    StoneApplication::Configuration      configuration_;
     IMessageEmitter&                     emitter_;
-    Orthanc::WebServiceParameters        orthanc_;
-    std::string                          rootDirectory_;
     Orthanc::SharedMessageQueue          queue_;
     State                                state_;
     boost::mutex                         mutex_;
     std::vector<boost::thread*>          workers_;
     boost::shared_ptr<SleepingCommands>  sleepingCommands_;
     boost::thread                        sleepingWorker_;
-    unsigned int                         sleepingTimeResolution_;
 
 #if ORTHANC_ENABLE_DCMTK == 1
     boost::shared_ptr<ParsedDicomCache>  dicomCache_;
@@ -90,19 +88,10 @@
     void StopInternal();
 
   public:
-    explicit ThreadedOracle(IMessageEmitter& emitter);
-
-    virtual ~ThreadedOracle();
-
-    void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc);
+    ThreadedOracle(const StoneApplication::Configuration& configuration,
+                   IMessageEmitter& emitter);
 
-    void SetRootDirectory(const std::string& rootDirectory);
-
-    void SetThreadsCount(unsigned int count);
-
-    void SetSleepingTimeResolution(unsigned int milliseconds);
-
-    void SetDicomCacheSize(size_t size);
+    virtual ~ThreadedOracle() ORTHANC_OVERRIDE;
 
     void Start();
 
@@ -118,16 +107,19 @@
 
   namespace New
   {
-    class ThreadedOracle : public IOracle
+    class ThreadedOracle :
+      public ::OrthancStone::IOracle,  // TODO Refactoring - Remove this
+      public ::OrthancStone::New::IOracle
     {
     private:
       class SleepRunnable;
 
-      RunnableThread       sleepingThread_;
-      Orthanc::ThreadPool  threadPool_;
+      StoneApplication::Configuration  configuration_;
+      RunnableThread                   sleepingThread_;
+      Orthanc::ThreadPool              threadPool_;
 
     public:
-      ThreadedOracle(unsigned int countWorkers);
+      ThreadedOracle(const StoneApplication::Configuration& configuration);
 
       void Start();
 
@@ -136,6 +128,9 @@
       virtual void Submit(IEnvironment& environment,
                           const boost::shared_ptr<IOracleClient>& client,
                           IOracleCommand* command /* takes ownership */) ORTHANC_OVERRIDE;
+
+      virtual bool Schedule(boost::shared_ptr<IObserver> receiver,
+                            IOracleCommand* command) ORTHANC_OVERRIDE;
     };
   }
 }
--- a/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -77,11 +77,12 @@
   };
     
 
-  WebAssemblyLoadersContext::WebAssemblyLoadersContext(unsigned int maxHighPriority,
+  WebAssemblyLoadersContext::WebAssemblyLoadersContext(const StoneApplication::Configuration& configuration,
+                                                       unsigned int maxHighPriority,
                                                        unsigned int maxStandardPriority,
-                                                       unsigned int maxLowPriority)
+                                                       unsigned int maxLowPriority) :
+    oracle_(configuration)
   {
-    oracle_.GetOracleObservable();
     scheduler_ = OracleScheduler::Create(oracle_, oracle_.GetOracleObservable(), oracle_,
                                          maxHighPriority, maxStandardPriority, maxLowPriority);
 
--- a/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.h	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.h	Fri Sep 25 18:24:15 2026 +0200
@@ -31,6 +31,7 @@
 
 namespace OrthancStone
 {
+  // TODO Refactoring - Remove this class
   class WebAssemblyLoadersContext : public ILoadersContext
   {
   private:
@@ -41,25 +42,11 @@
     std::list< boost::shared_ptr<IObserver> >  loaders_;
     
   public:
-    WebAssemblyLoadersContext(unsigned int maxHighPriority,
+    WebAssemblyLoadersContext(const StoneApplication::Configuration& configuration,
+                              unsigned int maxHighPriority,
                               unsigned int maxStandardPriority,
                               unsigned int maxLowPriority);
 
-    void SetLocalOrthanc(const std::string& root)
-    {
-      oracle_.SetLocalOrthanc(root);
-    }
-
-    void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc)
-    {
-      oracle_.SetRemoteOrthanc(orthanc);
-    }
-
-    void SetDicomCacheSize(size_t size)
-    {
-      oracle_.SetDicomCacheSize(size);
-    }
-
     WebAssemblyOracle::CachedInstanceAccessor* AccessCachedInstance(const std::string& sopInstanceUid)
     {
       return new WebAssemblyOracle::CachedInstanceAccessor(oracle_, sopInstanceUid);
--- a/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -384,24 +384,24 @@
       
       switch (method_)
       {
-        case Orthanc::HttpMethod_Get:
-          method = "GET";
-          break;
+      case Orthanc::HttpMethod_Get:
+        method = "GET";
+        break;
 
-        case Orthanc::HttpMethod_Post:
-          method = "POST";
-          break;
+      case Orthanc::HttpMethod_Post:
+        method = "POST";
+        break;
 
-        case Orthanc::HttpMethod_Delete:
-          method = "DELETE";
-          break;
+      case Orthanc::HttpMethod_Delete:
+        method = "DELETE";
+        break;
 
-        case Orthanc::HttpMethod_Put:
-          method = "PUT";
-          break;
+      case Orthanc::HttpMethod_Put:
+        method = "PUT";
+        break;
 
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
       }
 
       strcpy(attr.requestMethod, method);
@@ -464,59 +464,59 @@
   {
     switch (callback.GetCommand().GetType())
     {
-      case IOracleCommand::Type_Http:
-      {
-        callback.NotifySuccess(new HttpCommand::SuccessMessage(
-                                 dynamic_cast<const HttpCommand&>(callback.GetCommand()), headers, answer));
-        break;
-      }
+    case IOracleCommand::Type_Http:
+    {
+      callback.NotifySuccess(new HttpCommand::SuccessMessage(
+                               dynamic_cast<const HttpCommand&>(callback.GetCommand()), headers, answer));
+      break;
+    }
 
-      case IOracleCommand::Type_OrthancRestApi:
-      {
-        LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call EmitMessage(message);";
-        callback.NotifySuccess(new OrthancRestApiCommand::SuccessMessage(
-                                 dynamic_cast<const OrthancRestApiCommand&>(callback.GetCommand()), headers, answer));
-        break;
-      }
+    case IOracleCommand::Type_OrthancRestApi:
+    {
+      LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call EmitMessage(message);";
+      callback.NotifySuccess(new OrthancRestApiCommand::SuccessMessage(
+                               dynamic_cast<const OrthancRestApiCommand&>(callback.GetCommand()), headers, answer));
+      break;
+    }
 
-      case IOracleCommand::Type_GetOrthancImage:
-      {
-        dynamic_cast<const GetOrthancImageCommand&>(callback.GetCommand()).ProcessHttpAnswer(callback, answer, headers);
-        break;
-      }
+    case IOracleCommand::Type_GetOrthancImage:
+    {
+      dynamic_cast<const GetOrthancImageCommand&>(callback.GetCommand()).ProcessHttpAnswer(callback, answer, headers);
+      break;
+    }
 
-      case IOracleCommand::Type_GetOrthancWebViewerJpeg:
-      {
-        dynamic_cast<const GetOrthancWebViewerJpegCommand&>(callback.GetCommand()).ProcessHttpAnswer(callback, answer);
-        break;
-      }
+    case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+    {
+      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(callback.GetCommand()).ProcessHttpAnswer(callback, answer);
+      break;
+    }
 
-      case IOracleCommand::Type_ParseDicomFromWado:
-      {
+    case IOracleCommand::Type_ParseDicomFromWado:
+    {
 #if ORTHANC_ENABLE_DCMTK == 1
-        const ParseDicomFromWadoCommand& c = dynamic_cast<const ParseDicomFromWadoCommand&>(callback.GetCommand());
+      const ParseDicomFromWadoCommand& c = dynamic_cast<const ParseDicomFromWadoCommand&>(callback.GetCommand());
               
-        size_t fileSize;
-        std::unique_ptr<Orthanc::ParsedDicomFile> dicom
-          (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers));
+      size_t fileSize;
+      std::unique_ptr<Orthanc::ParsedDicomFile> dicom
+        (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers));
 
-        callback.NotifySuccess(new ParseDicomSuccessMessage(c, c.GetSource(), *dicom, fileSize, true));
+      callback.NotifySuccess(new ParseDicomSuccessMessage(c, c.GetSource(), *dicom, fileSize, true));
 
-        if (dicomCache_.get())
-        {
-          // Store it into the cache for future use
-          dicomCache_->Acquire(BUCKET_SOP, c.GetSopInstanceUid(), dicom.release(), fileSize, true);
-        }
+      if (dicomCache_.get())
+      {
+        // Store it into the cache for future use
+        dicomCache_->Acquire(BUCKET_SOP, c.GetSopInstanceUid(), dicom.release(), fileSize, true);
+      }
 #else
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
 #endif
-        break;
-      }
+      break;
+    }
 
-      default:
-        LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): "
-                   << callback.GetCommand().GetType();
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    default:
+      LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): "
+                 << callback.GetCommand().GetType();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
   }
 
@@ -524,18 +524,20 @@
   void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command,
                                         const std::string& uri) const
   {
-    if (isLocalOrthanc_)
+    if (configuration_.IsLocalOrthanc())
     {
-      command.SetUrl(StoneToolbox::JoinUrl(localOrthancRoot_, uri));
+      command.SetUrl(StoneToolbox::JoinUrl(configuration_.GetLocalOrthancRoot(), uri));
     }
     else
     {
-      command.SetUrl(StoneToolbox::JoinUrl(remoteOrthanc_.GetUrl(), uri));
-      command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders());
+      const Orthanc::WebServiceParameters& orthanc = configuration_.GetRemoteOrthancParameters();
       
-      if (!remoteOrthanc_.GetUsername().empty())
+      command.SetUrl(StoneToolbox::JoinUrl(orthanc.GetUrl(), uri));
+      command.AddHttpHeaders(orthanc.GetHttpHeaders());
+
+      if (!orthanc.GetUsername().empty())
       {
-        command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword());
+        command.SetCredentials(orthanc.GetUsername(), orthanc.GetPassword());
       }
     }
   }
@@ -658,54 +660,54 @@
 
     switch (command.GetRestCommand().GetType())
     {
-      case IOracleCommand::Type_Http:
+    case IOracleCommand::Type_Http:
+    {
+      const HttpCommand& rest =
+        dynamic_cast<const HttpCommand&>(command.GetRestCommand());
+
+      FetchCommand fetch(*this, protection.release());
+
+      fetch.SetMethod(rest.GetMethod());
+      fetch.SetUrl(rest.GetUrl());
+      fetch.AddHttpHeaders(rest.GetHttpHeaders());
+      fetch.SetTimeout(rest.GetTimeout());
+
+      if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
+          rest.GetMethod() == Orthanc::HttpMethod_Put)
       {
-        const HttpCommand& rest =
-          dynamic_cast<const HttpCommand&>(command.GetRestCommand());
-        
-        FetchCommand fetch(*this, protection.release());
+        std::string body = rest.GetBody();
+        fetch.SetBody(body);
+      }
     
-        fetch.SetMethod(rest.GetMethod());
-        fetch.SetUrl(rest.GetUrl());
-        fetch.AddHttpHeaders(rest.GetHttpHeaders());
-        fetch.SetTimeout(rest.GetTimeout());
-    
-        if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
-            rest.GetMethod() == Orthanc::HttpMethod_Put)
-        {
-          std::string body = rest.GetBody();
-          fetch.SetBody(body);
-        }
-    
-        fetch.Execute();
-        break;
+      fetch.Execute();
+      break;
+    }
+
+    case IOracleCommand::Type_OrthancRestApi:
+    {
+      const OrthancRestApiCommand& rest =
+        dynamic_cast<const OrthancRestApiCommand&>(command.GetRestCommand());
+
+      FetchCommand fetch(*this, protection.release());
+
+      fetch.SetMethod(rest.GetMethod());
+      SetOrthancUrl(fetch, rest.GetUri());
+      fetch.AddHttpHeaders(rest.GetHttpHeaders());
+      fetch.SetTimeout(rest.GetTimeout());
+
+      if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
+          rest.GetMethod() == Orthanc::HttpMethod_Put)
+      {
+        std::string body = rest.GetBody();
+        fetch.SetBody(body);
       }
 
-      case IOracleCommand::Type_OrthancRestApi:
-      {
-        const OrthancRestApiCommand& rest =
-          dynamic_cast<const OrthancRestApiCommand&>(command.GetRestCommand());
-        
-        FetchCommand fetch(*this, protection.release());
-
-        fetch.SetMethod(rest.GetMethod());
-        SetOrthancUrl(fetch, rest.GetUri());
-        fetch.AddHttpHeaders(rest.GetHttpHeaders());
-        fetch.SetTimeout(rest.GetTimeout());
+      fetch.Execute();
+      break;
+    }
 
-        if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
-            rest.GetMethod() == Orthanc::HttpMethod_Put)
-        {
-          std::string body = rest.GetBody();
-          fetch.SetBody(body);
-        }
-
-        fetch.Execute();
-        break;
-      }
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
   }
 
@@ -721,55 +723,74 @@
 
     switch (protection->GetCommand().GetType())
     {
-      case IOracleCommand::Type_Http:
-      {
-        FetchCommand fetch(*this, protection.release());
-        ExecuteHttpCommand(fetch);
-        break;
-      }
+    case IOracleCommand::Type_Http:
+    {
+      FetchCommand fetch(*this, protection.release());
+      ExecuteHttpCommand(fetch);
+      break;
+    }
         
-      case IOracleCommand::Type_OrthancRestApi:
-      {
-        FetchCommand fetch(*this, protection.release());
-        ExecuteOrthancRestApiCommand(fetch);
-        break;
-      }
+    case IOracleCommand::Type_OrthancRestApi:
+    {
+      FetchCommand fetch(*this, protection.release());
+      ExecuteOrthancRestApiCommand(fetch);
+      break;
+    }
         
-      case IOracleCommand::Type_GetOrthancImage:
-      {
-        FetchCommand fetch(*this, protection.release());
-        ExecuteGetOrthancImageCommand(fetch);
-        break;
-      }
+    case IOracleCommand::Type_GetOrthancImage:
+    {
+      FetchCommand fetch(*this, protection.release());
+      ExecuteGetOrthancImageCommand(fetch);
+      break;
+    }
 
-      case IOracleCommand::Type_GetOrthancWebViewerJpeg:
-      {
-        FetchCommand fetch(*this, protection.release());
-        ExecuteGetOrthancWebViewerJpegCommand(fetch);
-        break;
-      }
+    case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+    {
+      FetchCommand fetch(*this, protection.release());
+      ExecuteGetOrthancWebViewerJpegCommand(fetch);
+      break;
+    }
             
-      case IOracleCommand::Type_Sleep:
-      {
-        unsigned int timeoutMS = dynamic_cast<const SleepOracleCommand&>(protection->GetCommand()).GetDelay();
-        emscripten_set_timeout(TimeoutCallback, timeoutMS, protection.release());
-        break;
-      }
+    case IOracleCommand::Type_Sleep:
+    {
+      unsigned int timeoutMS = dynamic_cast<const SleepOracleCommand&>(protection->GetCommand()).GetDelay();
+      emscripten_set_timeout(TimeoutCallback, timeoutMS, protection.release());
+      break;
+    }
+
+    case IOracleCommand::Type_ParseDicomFromWado:
+#if ORTHANC_ENABLE_DCMTK == 1
+      ExecuteParseDicomFromWadoCommand(protection.release());
+#else
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
+                                      "DCMTK must be enabled to parse DICOM files");
+#endif
+      break;
             
-      case IOracleCommand::Type_ParseDicomFromWado:
+    default:
+      LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): "
+                 << protection->GetCommand().GetType();
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+
+  WebAssemblyOracle::WebAssemblyOracle(const StoneApplication::Configuration& configuration) :
+    configuration_(configuration)
+  {
 #if ORTHANC_ENABLE_DCMTK == 1
-        ExecuteParseDicomFromWadoCommand(protection.release());
+    if (configuration.GetDicomCacheSize() == 0)
+    {
+      LOG(WARNING) << "The DICOM cache is disabled";
+    }
+    else
+    {
+      LOG(INFO) << "The DICOM cache size is set to " << configuration.GetDicomCacheSize() << " bytes";
+      dicomCache_.reset(new ParsedDicomCache(configuration.GetDicomCacheSize()));
+    }
 #else
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
-                                        "DCMTK must be enabled to parse DICOM files");
+    LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled";
 #endif
-        break;
-            
-      default:
-        LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): "
-                   << protection->GetCommand().GetType();
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
   }
 
 
@@ -793,23 +814,6 @@
   }
 
 
-  void WebAssemblyOracle::SetDicomCacheSize(size_t size)
-  {
-#if ORTHANC_ENABLE_DCMTK == 1
-    if (size == 0)
-    {
-      dicomCache_.reset();
-    }
-    else
-    {
-      dicomCache_.reset(new ParsedDicomCache(size));
-    }
-#else
-    LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled";
-#endif
-  }
-
-  
   WebAssemblyOracle::CachedInstanceAccessor::CachedInstanceAccessor(WebAssemblyOracle& oracle,
                                                                     const std::string& sopInstanceUid)
   {
@@ -821,6 +825,7 @@
 #endif
   }
 
+
   bool WebAssemblyOracle::CachedInstanceAccessor::IsValid() const
   {
 #if ORTHANC_ENABLE_DCMTK == 1
@@ -831,6 +836,7 @@
 #endif
   }
 
+
 #if ORTHANC_ENABLE_DCMTK == 1
   const Orthanc::ParsedDicomFile& WebAssemblyOracle::CachedInstanceAccessor::GetDicom() const
   {
@@ -846,6 +852,7 @@
   }
 #endif
 
+
   size_t WebAssemblyOracle::CachedInstanceAccessor::GetFileSize() const
   {
 #if ORTHANC_ENABLE_DCMTK == 1
@@ -861,6 +868,7 @@
     }
   }
 
+
   bool WebAssemblyOracle::CachedInstanceAccessor::HasPixelData() const
   {
 #if ORTHANC_ENABLE_DCMTK == 1
--- a/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h	Fri Sep 25 18:24:15 2026 +0200
@@ -35,10 +35,11 @@
 
 #include "../../Oracle/OracleCallback.h"   // TODO Refactoring
 
+#include "../../Messages/IMessageEmitter.h"
 #include "../../Messages/IObservable.h"
-#include "../../Messages/IMessageEmitter.h"
 #include "../../Oracle/IEnvironment.h"
 #include "../../Oracle/IOracle.h"
+#include "../../StoneApplication.h"
 
 #if ORTHANC_ENABLE_DCMTK == 1
 #  include "../../Toolbox/ParsedDicomCache.h"
@@ -75,10 +76,8 @@
     
     void ExecuteParseDicomFromWadoCommand(IOracleCallback* callback);
 
-    IObservable                    oracleObservable_;
-    bool                           isLocalOrthanc_;
-    std::string                    localOrthancRoot_;
-    Orthanc::WebServiceParameters  remoteOrthanc_;
+    StoneApplication::Configuration  configuration_;
+    IObservable                      oracleObservable_;
 
 #if ORTHANC_ENABLE_DCMTK == 1
     std::unique_ptr<ParsedDicomCache>  dicomCache_;
@@ -91,10 +90,7 @@
     void Submit(IOracleCallback* callback);
 
   public:
-    WebAssemblyOracle() :
-      isLocalOrthanc_(false)
-    {
-    }
+    WebAssemblyOracle(const StoneApplication::Configuration& configuration);
     
     virtual void EmitMessage(boost::weak_ptr<IObserver> observer,
                              const IMessage& message) ORTHANC_OVERRIDE
@@ -114,20 +110,6 @@
       return oracleObservable_;
     }
 
-    void SetLocalOrthanc(const std::string& root)
-    {
-      isLocalOrthanc_ = true;
-      localOrthancRoot_ = root;
-    }
-
-    void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc)
-    {
-      isLocalOrthanc_ = false;
-      remoteOrthanc_ = orthanc;
-    }
-
-    void SetDicomCacheSize(size_t size);
-
     class CachedInstanceAccessor : public boost::noncopyable
     {
     private:
--- a/OrthancStone/Sources/StoneApplication.cpp	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/StoneApplication.cpp	Fri Sep 25 18:24:15 2026 +0200
@@ -40,6 +40,82 @@
 
 namespace OrthancStone
 {
+  StoneApplication::Configuration::Configuration() :
+    isLocalOrthanc_(false),
+    rootDirectory_("."),
+    oracleThreadsCount_(4),
+    workersTimeResolution_(50),  // By default, time resolution of 50ms
+    dicomCacheSize_(0)  // By default, no DICOM cache
+  {
+  }
+
+
+  void StoneApplication::Configuration::SetRemoteOrthancParameters(const Orthanc::WebServiceParameters& orthanc)
+  {
+    isLocalOrthanc_ = false;
+    remoteOrthanc_ = orthanc;
+  }
+
+
+  const Orthanc::WebServiceParameters& StoneApplication::Configuration::GetRemoteOrthancParameters() const
+  {
+    if (isLocalOrthanc_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return remoteOrthanc_;
+    }
+  }
+
+
+  void StoneApplication::Configuration::SetLocalOrthancRoot(const std::string& root)
+  {
+    isLocalOrthanc_ = true;
+    localOrthancRoot_ = root;
+  }
+
+
+  const std::string& StoneApplication::Configuration::GetLocalOrthancRoot() const
+  {
+    if (isLocalOrthanc_)
+    {
+      return localOrthancRoot_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  void StoneApplication::Configuration::SetOracleThreadsCount(unsigned int count)
+  {
+    if (count == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      oracleThreadsCount_ = count;
+    }
+  }
+
+
+  void StoneApplication::Configuration::SetWorkersTimeResolution(unsigned int milliseconds)
+  {
+    if (milliseconds == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      workersTimeResolution_ = milliseconds;
+    }
+  }
+
+
 #if ORTHANC_STONE_TARGET_PLATFORM_WASM == 1
   class StoneApplication::PImpl
   {
@@ -48,6 +124,11 @@
     WebAssemblyOracle       oracle_;
 
   public:
+    PImpl(const Configuration& configuration) :
+      oracle_(configuration)
+    {
+    }
+
     IEnvironment& GetEnvironment()
     {
       return environment_;
@@ -77,8 +158,8 @@
     New::ThreadedOracle  oracle_;
 
   public:
-    PImpl(unsigned int oracleThreads) :
-      oracle_(oracleThreads)
+    PImpl(const Configuration& configuration) :
+      oracle_(configuration)
     {
     }
 
@@ -109,14 +190,13 @@
 
   static Orthanc::Mutex                     applicationMutex_;
   static std::unique_ptr<StoneApplication>  application_;
-  static unsigned int                       threadsCount_ = 4;
 
-  StoneApplication::StoneApplication()
+  StoneApplication::StoneApplication(const Configuration& configuration)
   {
 #if ORTHANC_STONE_TARGET_PLATFORM_WASM == 1
-    pimpl_ = new PImpl;
+    pimpl_ = new PImpl(configuration);
 #elif ORTHANC_STONE_TARGET_PLATFORM_NATIVE == 1
-    pimpl_ = new PImpl(threadsCount_);
+    pimpl_ = new PImpl(configuration);
 #else
 #   error Support your platform here
 #endif
@@ -143,13 +223,13 @@
   }
 
 
-  void StoneApplication::Initialize()
+  void StoneApplication::Initialize(const Configuration& configuration)
   {
     Orthanc::Mutex::ScopedLock lock(applicationMutex_);
 
     if (application_.get() == NULL)
     {
-      application_.reset(new StoneApplication);
+      application_.reset(new StoneApplication(configuration));
       application_->pimpl_->Start();
     }
     else
@@ -184,18 +264,4 @@
     assert(pimpl_ != NULL);
     pimpl_->GetOracle().Submit(pimpl_->GetEnvironment(), client, command);
   }
-
-
-  void StoneApplication::SetThreadsCount(unsigned int count)
-  {
-    if (count == 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
-    }
-    else
-    {
-      Orthanc::Mutex::ScopedLock lock(applicationMutex_);
-      threadsCount_ = count;
-    }
-  }
 }
--- a/OrthancStone/Sources/StoneApplication.h	Fri Sep 25 14:02:54 2026 +0200
+++ b/OrthancStone/Sources/StoneApplication.h	Fri Sep 25 18:24:15 2026 +0200
@@ -25,23 +25,89 @@
 
 #include "Oracle/IEnvironment.h"
 
-#include <string>
+#include <WebServiceParameters.h>
 
 
 namespace OrthancStone
 {
   class StoneApplication : public boost::noncopyable
   {
+  public:
+    class Configuration
+    {
+    private:
+      bool                            isLocalOrthanc_;
+      Orthanc::WebServiceParameters   remoteOrthanc_;
+      std::string                     localOrthancRoot_;
+      std::string                     rootDirectory_;
+      unsigned int                    oracleThreadsCount_;
+      unsigned int                    workersTimeResolution_;
+      size_t                          dicomCacheSize_;
+
+    public:
+      Configuration();
+
+      bool IsLocalOrthanc() const
+      {
+        return isLocalOrthanc_;
+      }
+
+      void SetRemoteOrthancParameters(const Orthanc::WebServiceParameters& orthanc);
+
+      const Orthanc::WebServiceParameters& GetRemoteOrthancParameters() const;
+
+      // Using a local Orthanc only makes sense for WebAssembly, if it
+      // is Orthanc that serves the Web application
+      void SetLocalOrthancRoot(const std::string& root);
+
+      const std::string& GetLocalOrthancRoot() const;
+
+      void SetRootDirectory(const std::string& root)
+      {
+        rootDirectory_ = root;
+      }
+
+      const std::string& GetRootDirectory() const
+      {
+        return rootDirectory_;
+      }
+
+      void SetOracleThreadsCount(unsigned int count);
+
+      unsigned int GetOracleThreadsCount() const
+      {
+        return oracleThreadsCount_;
+      }
+
+      void SetWorkersTimeResolution(unsigned int milliseconds);
+
+      unsigned int GetWorkersTimeResolution() const
+      {
+        return workersTimeResolution_;
+      }
+
+      // Setting the cache size to zero disables it
+      void SetDicomCacheSize(size_t size)
+      {
+        dicomCacheSize_ = size;
+      }
+
+      size_t GetDicomCacheSize() const
+      {
+        return dicomCacheSize_;
+      }
+    };
+
   private:
     class PImpl;
     PImpl* pimpl_;
 
-    StoneApplication();
+    StoneApplication(const Configuration& configuration);
 
   public:
-    static StoneApplication& GetInstance();
+    static void Initialize(const Configuration& configuration);
 
-    static void Initialize();
+    static StoneApplication& GetInstance();
 
     static void Finalize();
 
@@ -51,7 +117,5 @@
 
     void Submit(const boost::shared_ptr<IOracleClient>& client,
                 IOracleCommand* command /* takes ownership */);
-
-    static void SetThreadsCount(unsigned int count);
   };
 }