changeset 2194:c4c6884b5f56

support for large OsiriX files
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 18 Apr 2025 17:37:07 +0200
parents e7a36e84f808
children fe1ee95aa12c 1c5d4541cfb5
files Applications/StoneWebViewer/WebApplication/app.js Applications/StoneWebViewer/WebAssembly/CMakeLists.txt Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp
diffstat 4 files changed, 69 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/StoneWebViewer/WebApplication/app.js	Fri Apr 18 14:37:14 2025 +0200
+++ b/Applications/StoneWebViewer/WebApplication/app.js	Fri Apr 18 17:37:07 2025 +0200
@@ -1778,6 +1778,23 @@
 document.onselectstart = new Function ('return false');
 
 
+
+function CallLoadOsirixAnnotations(xml, clear) {
+  /**
+   * XML files from OsiriX can be very large, so it is not possible to
+   * send them to WebAssembly as a "string" parameter, otherwise it
+   * may result in a stack overflow. We thus have to copy the
+   * JavaScript string into the WebAssembly heap.
+   * https://thewasmfrontier.hashnode.dev/working-with-string-in-webassembly
+   **/
+  var length = Module.lengthBytesUTF8(xml);
+  var pointer = stone.Allocate(length + 1);
+  Module.stringToUTF8(xml, pointer, length + 1);
+  stone.LoadOsiriXAnnotations(pointer, length + 1, clear);
+  stone.Deallocate(pointer);
+}
+
+
 window.addEventListener('message', function(e) {
   if ('type' in e.data) {
     if (e.data.type == 'show-osirix-annotations') {
@@ -1797,12 +1814,21 @@
         if ('clear' in e.data) {
           clear = e.data.clear;
         }
-        
-        app.LoadOsiriXAnnotations(e.data.xml, clear);
+
+        if ('xml' in e.data) {
+          CallLoadOsirixAnnotations(e.data.xml, clear);
+        } else if ('url' in e.data) {
+          axios.get(e.data.url)
+            .then(function(response) {
+              CallLoadOsirixAnnotations(response.data, clear);
+            });
+        } else {
+          console.error('Neither "xml", nor "url" was provided to load OsiriX annotations from');
+        }
       }
     }
     else {
-      console.log('Unknown type of dynamic action in the Stone Web viewer: ' + e.data.type);
+      console.error('Unknown type of dynamic action in the Stone Web viewer: ' + e.data.type);
     }
   }
 });
--- a/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt	Fri Apr 18 14:37:14 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt	Fri Apr 18 17:37:07 2025 +0200
@@ -36,7 +36,7 @@
   set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
 endif()
 
-set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\", \"lengthBytesUTF8\", \"stringToUTF8\"]'")
 set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1")
 set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456")  # 256MB + resize
 set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
--- a/Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py	Fri Apr 18 14:37:14 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py	Fri Apr 18 17:37:07 2025 +0200
@@ -160,6 +160,9 @@
             elif returnType in [ 'int', 'unsigned int' ]:
                 f['hasReturn'] = True
                 f['returnType'] = "'int'"
+            elif returnType == 'void *':
+                f['hasReturn'] = True
+                f['returnType'] = "'int'"
             else:
                 raise Exception('Unknown return type in function "%s()": %s' % (node.spelling, returnType))
 
@@ -176,6 +179,10 @@
                         arg['type'] = "'string'"
                     elif argType == 'double':
                         arg['type'] = "'double'"
+                    elif argType == 'size_t':
+                        arg['type'] = "'int'"
+                    elif argType in [ 'const void *', 'void *' ]:
+                        arg['type'] = "'int'"
                     else:
                         raise Exception('Unknown type for argument "%s" in function "%s()": %s' %
                                         (child.displayname, node.spelling, argType))
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Fri Apr 18 14:37:14 2025 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Fri Apr 18 17:37:07 2025 +0200
@@ -4125,6 +4125,15 @@
 }
 
 
+
+typedef void (*PluginInitializer) ();
+
+static const PluginInitializer pluginsInitializers_[] = {
+  NULL
+};
+
+
+
 extern "C"
 {
   int main(int argc, char const *argv[]) 
@@ -4143,6 +4152,11 @@
     instancesCache_.reset(new InstancesCache);
     osiriXAnnotations_.reset(new OrthancStone::OsiriX::CollectionOfAnnotations);
 
+    for (size_t i = 0; pluginsInitializers_[i] != NULL; i++)
+    {
+      pluginsInitializers_[i] ();
+    }
+
     DISPATCH_JAVASCRIPT_EVENT("StoneInitialized");
   }
 
@@ -4727,7 +4741,8 @@
   // Side-effect: "GetStringBuffer()" is filled with the "Series
   // Instance UID" of the first loaded annotation
   EMSCRIPTEN_KEEPALIVE
-  int LoadOsiriXAnnotations(const char* xml,
+  int LoadOsiriXAnnotations(const void* xmlPointer,
+                            int xmlSize,
                             int clearPreviousAnnotations)
   {
     try
@@ -4736,8 +4751,8 @@
       {
         osiriXAnnotations_->Clear();
       }
-      
-      osiriXAnnotations_->LoadXml(xml);
+
+      osiriXAnnotations_->LoadXml(reinterpret_cast<const char*>(xmlPointer), xmlSize);
       
       // Force redraw, as the annotations might have changed
       for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
@@ -4895,4 +4910,18 @@
     }
     EXTERN_CATCH_EXCEPTIONS;
   }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void *Allocate(size_t size)
+  {
+    return malloc(size);
+  }
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void Deallocate(void* ptr)
+  {
+    free(ptr);
+  }
 }