# HG changeset patch # User Benjamin Golinvaux # Date 1588168458 -7200 # Node ID 27e0a00bd3e8190a22677254150f731c67e06194 # Parent ffdb82850e983078a9a9f0790a961e27c166c1d1 RtViewer SingleFrameViewer OK : wasm SDL single viewport other viewports ongoing diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/Common/RtViewer.cpp --- a/Samples/Common/RtViewer.cpp Tue Apr 28 13:52:21 2020 +0200 +++ b/Samples/Common/RtViewer.cpp Wed Apr 29 15:54:18 2020 +0200 @@ -20,6 +20,7 @@ // Sample app #include "RtViewer.h" +#include "SampleHelpers.h" // Stone of Orthanc #include @@ -39,16 +40,12 @@ #include #include -#include #include #include #include #include // Orthanc -#include -#include -#include #include #include @@ -249,8 +246,8 @@ , currentPlane_(0) , projection_(VolumeProjection_Coronal) { - // False means we do NOT let Windows treat this as a legacy application that needs to be scaled - viewport_ = SdlOpenGLViewport::Create("CT RTDOSE RTSTRUCT viewer", 1024, 1024, false); + // the viewport hosts the scene + CreateViewport(); std::unique_ptr lock(viewport_->Lock()); ViewportController& controller = lock->GetController(); @@ -326,35 +323,7 @@ activeTracker_.reset(); } } - - void RtViewerApp::TakeScreenshot(const std::string& target, - unsigned int canvasWidth, - unsigned int canvasHeight) - { - std::unique_ptr lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Scene2D& scene = controller.GetScene(); - - CairoCompositor compositor(canvasWidth, canvasHeight); - compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE_0, Orthanc::Encoding_Latin1); - compositor.Refresh(scene); - - Orthanc::ImageAccessor canvas; - compositor.GetCanvas().GetReadOnlyAccessor(canvas); - - Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); - Orthanc::ImageProcessing::Convert(png, canvas); - - Orthanc::PngWriter writer; - writer.WriteToFile(target, png); - } - - boost::shared_ptr RtViewerApp::TrackerHitTest(const PointerEvent& e) - { - // std::vector> measureTools_; - return boost::shared_ptr(); - } - + void RtViewerApp::PrepareLoadersAndSlicers() { @@ -369,7 +338,7 @@ // "true" means use progressive quality (jpeg 50 --> jpeg 90 --> 16-bit raw) // "false" means only using hi quality // TODO: add flag for quality - ctLoader_ = OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext_, ctVolume_, false); + ctLoader_ = OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext_, ctVolume_, true); // we need to store the CT loader to ask from geometry details later on when geometry is loaded geometryProvider_ = ctLoader_; @@ -421,13 +390,15 @@ this->SetStructureSet(LAYER_POSITION + 2, rtstructLoader_); #if 1 + ORTHANC_ASSERT(HasArgument("ctseries") && HasArgument("rtdose") && HasArgument("rtstruct")); + LOG(INFO) << "About to load:"; - LOG(INFO) << " CT : " << ctSeriesId_;; - LOG(INFO) << " RTDOSE : " << doseInstanceId_; - LOG(INFO) << " RTSTRUCT : " << rtStructInstanceId_; - ctLoader_->LoadSeries(ctSeriesId_); - doseLoader_->LoadInstance(doseInstanceId_); - rtstructLoader_->LoadInstanceFullVisibility(rtStructInstanceId_); + LOG(INFO) << " CT : " << GetArgument("ctseries"); + LOG(INFO) << " RTDOSE : " << GetArgument("rtdose"); + LOG(INFO) << " RTSTRUCT : " << GetArgument("rtstruct"); + ctLoader_->LoadSeries(GetArgument("ctseries")); + doseLoader_->LoadInstance(GetArgument("rtdose")); + rtstructLoader_->LoadInstanceFullVisibility(GetArgument("rtstruct")); #elif 0 /* @@ -547,6 +518,23 @@ structLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(scene, depth, volume)); } + void RtViewerApp::SetArgument(const std::string& key, const std::string& value) + { + if (key == "loglevel") + OrthancStoneHelpers::SetLogLevel(value); + else + arguments_[key] = value; + } + + const std::string& RtViewerApp::GetArgument(const std::string& key) const + { + ORTHANC_ASSERT(HasArgument(key)); + return arguments_.at(key); + } + bool RtViewerApp::HasArgument(const std::string& key) const + { + return (arguments_.find(key) != arguments_.end()); + } void RtViewerApp::SetInfoDisplayMessage( std::string key, std::string value) diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/Common/RtViewer.h --- a/Samples/Common/RtViewer.h Tue Apr 28 13:52:21 2020 +0200 +++ b/Samples/Common/RtViewer.h Wed Apr 29 15:54:18 2020 +0200 @@ -18,19 +18,18 @@ * along with this program. If not, see . **/ -#include -#include +#include + +#include +#include +#include +#include +#include #include -#include +#include #include #include #include -#include -#include -#include -#include - -#include #include #include @@ -88,6 +87,10 @@ void ProcessOptions(int argc, char* argv[]); void HandleApplicationEvent(const SDL_Event& event); #elif ORTHANC_ENABLE_WASM + public: + void RunWasm(); +#else +# error Either ORTHANC_ENABLE_SDL or ORTHANC_ENABLE_WASM must be enabled #endif public: @@ -95,6 +98,12 @@ void DisableTracker(); /** + Called by command-line option processing or when parsing the URL + parameters. + */ + void SetArgument(const std::string& key, const std::string& value); + + /** This method is called when the scene transform changes. It allows to recompute the visual elements whose content depend upon the scene transform */ @@ -136,56 +145,14 @@ private: void PrepareLoadersAndSlicers(); - - /** - 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 - URL. In our wasm samples, it is set to "..", because we set up either a reverse proxy or an Orthanc ServeFolders - plugin that serves the main web application from an URL like "http://localhost:8042/rtviewer" (with ".." leading - to the main Orthanc root URL) - */ - std::string orthancUrl_; - - /** - Orthanc ID of the CT series to load. Only used between startup and loading time. - */ - std::string ctSeriesId_; - - /** - Orthanc ID of the RTDOSE instance to load. Only used between startup and loading time. - */ - std::string doseInstanceId_; - - /** - Orthanc ID of the RTSTRUCT instance to load. Only used between startup and loading time. - */ - std::string rtStructInstanceId_; - - -#if ORTHANC_ENABLE_SDL - // if threaded (not wasm) - //IObservable oracleObservable_; - //ThreadedOracle oracle_; - //boost::shared_mutex mutex_; // to serialize messages from the ThreadedOracle -#elif ORTHANC_ENABLE_WASM - - -#endif - void SelectNextTool(); - /** - This returns a random point in the canvas part of the scene, but in - scene coordinates - */ - ScenePoint2D GetRandomPointInScene() const; + // argument handling + // SetArgument is above (public section) + std::map arguments_; - boost::shared_ptr TrackerHitTest(const PointerEvent& e); - - boost::shared_ptr CreateSuitableTracker( - const SDL_Event& event, - const PointerEvent& e); + const std::string& GetArgument(const std::string& key) const; + bool HasArgument(const std::string& key) const; void TakeScreenshot( const std::string& target, @@ -224,6 +191,7 @@ const boost::shared_ptr& volume); private: + void CreateViewport(); void DisplayFloatingCtrlInfoText(const PointerEvent& e); void DisplayInfoText(); void HideInfoText(); @@ -239,7 +207,8 @@ boost::shared_ptr rtstructLoader_; /** encapsulates resources shared by loaders */ - boost::shared_ptr loadersContext_; + boost::shared_ptr loadersContext_; + boost::shared_ptr ctVolumeLayerSource_, doseVolumeLayerSource_, structLayerSource_; /** @@ -270,7 +239,7 @@ RtViewerGuiTool currentTool_; boost::shared_ptr undoStack_; - boost::shared_ptr viewport_; + boost::shared_ptr viewport_; }; } diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/Sdl/RtViewer/RtViewerSdl.cpp --- a/Samples/Sdl/RtViewer/RtViewerSdl.cpp Tue Apr 28 13:52:21 2020 +0200 +++ b/Samples/Sdl/RtViewer/RtViewerSdl.cpp Wed Apr 29 15:54:18 2020 +0200 @@ -20,14 +20,24 @@ #include "RtViewer.h" #include "../SdlHelpers.h" -#include "SampleHelpers.h" +// Stone of Orthanc includes +#include +#include #include #include -#include +// Orthanc (a.o. for screenshot capture) +#include +#include +#include + #include +#include + +// #include this include might be necessary in more recent boost versions + #include #include @@ -51,6 +61,12 @@ namespace OrthancStone { + void RtViewerApp::CreateViewport() + { + // False means we do NOT let Windows treat this as a legacy application that needs to be scaled + viewport_ = SdlOpenGLViewport::Create("CT RTDOSE RTSTRUCT viewer", 1024, 1024, false); + } + void RtViewerApp::ProcessOptions(int argc, char* argv[]) { namespace po = boost::program_options; @@ -84,31 +100,12 @@ std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl; } - if (vm.count("loglevel") > 0) - { - std::string logLevel = vm["loglevel"].as(); - OrthancStoneHelpers::SetLogLevel(logLevel); - } - - if (vm.count("orthanc") > 0) + for (po::variables_map::iterator it = vm.begin(); it != vm.end(); ++it) { - // maybe check URL validity here - orthancUrl_ = vm["orthanc"].as(); - } - - if (vm.count("ctseries") > 0) - { - ctSeriesId_ = vm["ctseries"].as(); - } - - if (vm.count("rtdose") > 0) - { - doseInstanceId_ = vm["rtdose"].as(); - } - - if (vm.count("rtstruct") > 0) - { - rtStructInstanceId_ = vm["rtstruct"].as(); + std::string key = it->first; + const po::variable_value& value = it->second; + const std::string& strValue = value.as(); + SetArgument(key, strValue); } } @@ -139,7 +136,39 @@ Create the shared loaders context */ loadersContext_.reset(new GenericLoadersContext(1, 4, 1)); - loadersContext_->StartOracle(); + + // we are in SDL --> downcast to concrete type + boost::shared_ptr loadersContext = boost::dynamic_pointer_cast(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 + URL. In our wasm samples, it is set to "..", because we set up either a reverse proxy or an Orthanc ServeFolders + plugin that serves the main web application from an URL like "http://localhost:8042/rtviewer" (with ".." leading + to the main Orthanc root URL) + */ + std::string orthancUrl = arguments_["orthanc"]; + + { + Orthanc::WebServiceParameters p; + if (HasArgument("orthanc")) + { + p.SetUrl(orthancUrl); + } + if (HasArgument("user")) + { + ORTHANC_ASSERT(HasArgument("password")); + p.SetCredentials(GetArgument("user"), GetArgument("password")); + } + else + { + ORTHANC_ASSERT(!HasArgument("password")); + } + loadersContext->SetOrthancParameters(p); + } + + loadersContext->StartOracle(); /** It is very important that the Oracle (responsible for network I/O) be started before creating and firing the @@ -147,13 +176,38 @@ */ PrepareLoadersAndSlicers(); - OrthancStone::DefaultViewportInteractor interactor; + DefaultViewportInteractor interactor; + + boost::shared_ptr viewport = boost::dynamic_pointer_cast(viewport_); + + OrthancStoneHelpers::SdlRunLoop(viewport, interactor); + + loadersContext->StopOracle(); + } - OrthancStoneHelpers::SdlRunLoop(viewport_, interactor); + void RtViewerApp::TakeScreenshot(const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight) + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); - loadersContext_->StopOracle(); + CairoCompositor compositor(canvasWidth, canvasHeight); + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE_0, Orthanc::Encoding_Latin1); + compositor.Refresh(scene); + + Orthanc::ImageAccessor canvas; + compositor.GetCanvas().GetReadOnlyAccessor(canvas); + + Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); + Orthanc::ImageProcessing::Convert(png, canvas); + + Orthanc::PngWriter writer; + writer.WriteToFile(target, png); } + #if 0 void RtViewerApp::HandleApplicationEvent( const SDL_Event& event) diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/Sdl/SingleFrameViewer/SimpleViewer.cpp --- a/Samples/Sdl/SingleFrameViewer/SimpleViewer.cpp Tue Apr 28 13:52:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,293 +0,0 @@ -#error TO BE DELETED - -#include "SdlSimpleViewerApplication.h" - -#include - -#include -#include -#include -#include -#include - -#include - -namespace OrthancStone -{ - static KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState, - const int scancodeCount) - { - int result = KeyboardModifiers_None; - - if (keyboardState != NULL) - { - if (SDL_SCANCODE_LSHIFT < scancodeCount && - keyboardState[SDL_SCANCODE_LSHIFT]) - { - result |= KeyboardModifiers_Shift; - } - - if (SDL_SCANCODE_RSHIFT < scancodeCount && - keyboardState[SDL_SCANCODE_RSHIFT]) - { - result |= KeyboardModifiers_Shift; - } - - if (SDL_SCANCODE_LCTRL < scancodeCount && - keyboardState[SDL_SCANCODE_LCTRL]) - { - result |= KeyboardModifiers_Control; - } - - if (SDL_SCANCODE_RCTRL < scancodeCount && - keyboardState[SDL_SCANCODE_RCTRL]) - { - result |= KeyboardModifiers_Control; - } - - if (SDL_SCANCODE_LALT < scancodeCount && - keyboardState[SDL_SCANCODE_LALT]) - { - result |= KeyboardModifiers_Alt; - } - - if (SDL_SCANCODE_RALT < scancodeCount && - keyboardState[SDL_SCANCODE_RALT]) - { - result |= KeyboardModifiers_Alt; - } - } - - return static_cast(result); - } - - - static void GetPointerEvent(PointerEvent& p, - const ICompositor& compositor, - SDL_Event event, - const uint8_t* keyboardState, - const int scancodeCount) - { - KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); - - switch (event.button.button) - { - case SDL_BUTTON_LEFT: - p.SetMouseButton(OrthancStone::MouseButton_Left); - break; - - case SDL_BUTTON_RIGHT: - p.SetMouseButton(OrthancStone::MouseButton_Right); - break; - - case SDL_BUTTON_MIDDLE: - p.SetMouseButton(OrthancStone::MouseButton_Middle); - break; - - default: - p.SetMouseButton(OrthancStone::MouseButton_None); - break; - } - - p.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); - p.SetAltModifier(modifiers & KeyboardModifiers_Alt); - p.SetControlModifier(modifiers & KeyboardModifiers_Control); - p.SetShiftModifier(modifiers & KeyboardModifiers_Shift); - } - -} - -/** - * IMPORTANT: The full arguments to "main()" are needed for SDL on - * Windows. Otherwise, one gets the linking error "undefined reference - * to `SDL_main'". https://wiki.libsdl.org/FAQWindows - **/ -int main(int argc, char* argv[]) -{ - try - { - OrthancStone::StoneInitialize(); - Orthanc::Logging::EnableInfoLevel(true); - //Orthanc::Logging::EnableTraceLevel(true); - - { - -#if 1 - boost::shared_ptr viewport = - OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600); -#else - boost::shared_ptr viewport = - OrthancStone::SdlCairoViewport::Create("Stone of Orthanc", 800, 600); -#endif - - OrthancStone::GenericLoadersContext context(1, 4, 1); - - context.StartOracle(); - - { - - boost::shared_ptr application( - SdlSimpleViewerApplication::Create(context, viewport)); - - OrthancStone::DicomSource source; - - // Default and command-line parameters - const char* instanceId = "285dece8-e1956b38-cdc7d084-6ce3371e-536a9ffc"; - unsigned int frameIndex = 0; - - if (argc == 1) - { - LOG(ERROR) << "No instanceId supplied. The default of " << instanceId << " will be used. " - << "Please supply the Orthanc instance ID of the frame you wish to display then, optionally, " - << "the zero-based index of the frame (for multi-frame instances)"; - // TODO: frame number as second argument... - } - - if (argc >= 2) - instanceId = argv[1]; - - if (argc >= 3) - frameIndex = atoi(argv[1]); - - if (argc > 3) - { - LOG(ERROR) << "Extra arguments ignored!"; - } - - - application->LoadOrthancFrame(source, instanceId, frameIndex); - - OrthancStone::DefaultViewportInteractor interactor; - - { - int scancodeCount = 0; - const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); - - bool stop = false; - while (!stop) - { - bool paint = false; - SDL_Event event; - while (SDL_PollEvent(&event)) - { - if (event.type == SDL_QUIT) - { - stop = true; - break; - } - else if (viewport->IsRefreshEvent(event)) - { - paint = true; - } - else if (event.type == SDL_WINDOWEVENT && - (event.window.event == SDL_WINDOWEVENT_RESIZED || - event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) - { - viewport->UpdateSize(event.window.data1, event.window.data2); - } - else if (event.type == SDL_WINDOWEVENT && - (event.window.event == SDL_WINDOWEVENT_SHOWN || - event.window.event == SDL_WINDOWEVENT_EXPOSED)) - { - paint = true; - } - else if (event.type == SDL_KEYDOWN && - event.key.repeat == 0 /* Ignore key bounce */) - { - switch (event.key.keysym.sym) - { - case SDLK_f: - viewport->ToggleMaximize(); - break; - - case SDLK_s: - application->FitContent(); - break; - - case SDLK_q: - stop = true; - break; - - default: - break; - } - } - else if (event.type == SDL_MOUSEBUTTONDOWN || - event.type == SDL_MOUSEMOTION || - event.type == SDL_MOUSEBUTTONUP) - { - std::auto_ptr lock(viewport->Lock()); - if (lock->HasCompositor()) - { - OrthancStone::PointerEvent p; - OrthancStone::GetPointerEvent(p, lock->GetCompositor(), - event, keyboardState, scancodeCount); - - switch (event.type) - { - case SDL_MOUSEBUTTONDOWN: - lock->GetController().HandleMousePress(interactor, p, - lock->GetCompositor().GetCanvasWidth(), - lock->GetCompositor().GetCanvasHeight()); - lock->Invalidate(); - break; - - case SDL_MOUSEMOTION: - if (lock->GetController().HandleMouseMove(p)) - { - lock->Invalidate(); - } - break; - - case SDL_MOUSEBUTTONUP: - lock->GetController().HandleMouseRelease(p); - lock->Invalidate(); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - } - } - - if (paint) - { - viewport->Paint(); - } - - // Small delay to avoid using 100% of CPU - SDL_Delay(1); - } - } - - context.StopOracle(); - } - } - - OrthancStone::StoneFinalize(); - return 0; - } - catch (Orthanc::OrthancException & e) - { - auto test = e.What(); - fprintf(stdout, test); - LOG(ERROR) << "OrthancException: " << e.What(); - return -1; - } - catch (OrthancStone::StoneException & e) - { - LOG(ERROR) << "StoneException: " << e.What(); - return -1; - } - catch (std::runtime_error & e) - { - LOG(ERROR) << "Runtime error: " << e.what(); - return -1; - } - catch (...) - { - LOG(ERROR) << "Native exception"; - return -1; - } -} diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/WebAssembly/RtViewer/CMakeLists.txt --- a/Samples/WebAssembly/RtViewer/CMakeLists.txt Tue Apr 28 13:52:21 2020 +0200 +++ b/Samples/WebAssembly/RtViewer/CMakeLists.txt Wed Apr 29 15:54:18 2020 +0200 @@ -33,6 +33,7 @@ set(ORTHANC_FRAMEWORK_SOURCE "path") include(${STONE_ROOT}/Resources/CMake/OrthancStoneParameters.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) SET(ENABLE_DCMTK ON) SET(ENABLE_GOOGLE_TEST OFF) @@ -40,11 +41,25 @@ SET(ENABLE_WASM ON) SET(ORTHANC_SANDBOXED ON) + +# We embed a font to be used for on-screen overlays +# --------------------------------------------------------------- + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +set(ORTHANC_STONE_APPLICATION_RESOURCES + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + # this will set up the build system for Stone of Orthanc and will # populate the ORTHANC_STONE_SOURCES CMake variable include(${STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) include_directories(${STONE_ROOT}) +include_directories(../../Common) # Define the WASM module # --------------------------------------------------------------- @@ -71,9 +86,8 @@ # --------------------------------------------------------------- install( FILES - ${CMAKE_SOURCE_DIR}/RtViewerApp.js + ${CMAKE_SOURCE_DIR}/RtViewerWasmApp.js ${CMAKE_SOURCE_DIR}/index.html ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.wasm - ${CMAKE_SOURCE_DIR}/WasmWrapper.js DESTINATION ${CMAKE_INSTALL_PREFIX} ) diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/WebAssembly/RtViewer/RtViewerWasm.cpp --- a/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp Tue Apr 28 13:52:21 2020 +0200 +++ b/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp Wed Apr 29 15:54:18 2020 +0200 @@ -0,0 +1,170 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 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 . + **/ + +#include "RtViewer.h" +#include "SampleHelpers.h" + +// Stone of Orthanc includes +#include +//#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +// #include this include might be necessary in more recent boost versions + +#include +#include + + +#define DISPATCH_JAVASCRIPT_EVENT(name) \ + EM_ASM( \ + const customEvent = document.createEvent("CustomEvent"); \ + customEvent.initCustomEvent(name, false, false, undefined); \ + window.dispatchEvent(customEvent); \ + ); + +#define EXTERN_CATCH_EXCEPTIONS \ + catch (Orthanc::OrthancException& e) \ + { \ + LOG(ERROR) << "OrthancException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (OrthancStone::StoneException& e) \ + { \ + LOG(ERROR) << "StoneException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (std::exception& e) \ + { \ + LOG(ERROR) << "Runtime error: " << e.what(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (...) \ + { \ + LOG(ERROR) << "Native exception"; \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } + +namespace OrthancStone +{ + void RtViewerApp::CreateViewport() + { + viewport_ = WebGLViewport::Create("mycanvas1"); + } + + void RtViewerApp::TakeScreenshot(const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + void RtViewerApp::RunWasm() + { + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + ICompositor& compositor = lock->GetCompositor(); + + controller.FitContent(compositor.GetCanvasWidth(), compositor.GetCanvasHeight()); + + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_0, Orthanc::Encoding_Latin1); + compositor.SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_1, Orthanc::Encoding_Latin1); + } + + loadersContext_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1)); + + // we are in WASM --> downcast to concrete type + boost::shared_ptr loadersContext = + boost::dynamic_pointer_cast(loadersContext_); + + if (HasArgument("orthanc")) + loadersContext->SetLocalOrthanc(GetArgument("orthanc")); + else + loadersContext->SetLocalOrthanc(".."); + + loadersContext->SetDicomCacheSize(128 * 1024 * 1024); // 128MB + + PrepareLoadersAndSlicers(); + + DefaultViewportInteractor interactor; + } +} + +extern "C" +{ + boost::shared_ptr g_app; + + int main(int argc, char const *argv[]) + { + try + { + OrthancStone::StoneInitialize(); + Orthanc::Logging::Initialize(); + Orthanc::Logging::EnableTraceLevel(true); + + LOG(WARNING) << "Initializing native Stone"; + + LOG(WARNING) << "Compiled with Emscripten " << __EMSCRIPTEN_major__ + << "." << __EMSCRIPTEN_minor__ + << "." << __EMSCRIPTEN_tiny__; + + LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness()); + + g_app = OrthancStone::RtViewerApp::Create(); + + DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized"); + } + EXTERN_CATCH_EXCEPTIONS; + } + + EMSCRIPTEN_KEEPALIVE + void Initialize(const char* canvasId) + { + try + { + g_app->RunWasm(); + } + EXTERN_CATCH_EXCEPTIONS; + } + + EMSCRIPTEN_KEEPALIVE + void SetArgument(const char* key, const char* value) + { + // This is called for each GET argument (cf. "app.js") + LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]"; + g_app->SetArgument(key, value); + } + +} diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/WebAssembly/RtViewer/RtViewerWasm.html --- a/Samples/WebAssembly/RtViewer/RtViewerWasm.html Tue Apr 28 13:52:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ - - - - - - - - - - - - Stone of Orthanc - - - - - - - - - - - diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/WebAssembly/RtViewer/RtViewerWasmApp.js --- a/Samples/WebAssembly/RtViewer/RtViewerWasmApp.js Tue Apr 28 13:52:21 2020 +0200 +++ b/Samples/WebAssembly/RtViewer/RtViewerWasmApp.js Wed Apr 29 15:54:18 2020 +0200 @@ -1,11 +1,33 @@ -// Check support for WebAssembly -if (!('WebAssembly' in window)) { - alert('Sorry, your browser does not support WebAssembly :('); -} else { + +// This object wraps the functions exposed by the wasm module + +const WasmModuleWrapper = function() { + this._InitializeViewport = undefined; +}; + +WasmModuleWrapper.prototype.Setup = function(Module) { + this._SetArgument = Module.cwrap('SetArgument', null, [ 'string', 'string' ]); + this._Initialize = Module.cwrap('Initialize', null, [ 'string' ]); +}; - // Wait for the module to be loaded (the event "WebAssemblyLoaded" - // must be emitted by the "main" function) - window.addEventListener('WebAssemblyLoaded', function() { +WasmModuleWrapper.prototype.SetArgument = function(key, value) { + this._SetArgument(key, value); +}; + +WasmModuleWrapper.prototype.Initialize = function(canvasId) { + this._Initialize(canvasId); +}; + +var wasmModuleWrapper = new WasmModuleWrapper(); + +$(document).ready(function() { + + window.addEventListener('WasmModuleInitialized', function() { + + // bind the C++ global functions + wasmModuleWrapper.Setup(Module); + + console.warn('Native C++ module initialized'); // Loop over the GET arguments var parameters = window.location.search.substr(1); @@ -14,15 +36,50 @@ for (var i = 0; i < tokens.length; i++) { var arg = tokens[i].split('='); if (arg.length == 2) { - // Send each GET argument to WebAssembly - Module.ccall('SetArgument', null, [ 'string', 'string' ], - [ arg[0], decodeURIComponent(arg[1]) ]); + wasmModuleWrapper.SetArgument(arg[0], decodeURIComponent(arg[1])); } } } + wasmModuleWrapper.Initialize(); + }); - // Inform the WebAssembly module that it can start - Module.ccall('Initialize', null, null, null); - }); -} + window.addEventListener('StoneException', function() { + alert('Exception caught in C++ code'); + }); + + var scriptSource; + + if ('WebAssembly' in window) { + console.warn('Loading WebAssembly'); + scriptSource = 'RtViewerWasm.js'; + } else { + console.error('Your browser does not support WebAssembly!'); + } + + // Option 1: Loading script using plain HTML + + /* + var script = document.createElement('script'); + script.src = scriptSource; + script.type = 'text/javascript'; + document.body.appendChild(script); + */ + + // Option 2: Loading script using AJAX (gives the opportunity to + // report explicit errors) + + axios.get(scriptSource) + .then(function (response) { + var script = document.createElement('script'); + script.innerHTML = response.data; + script.type = 'text/javascript'; + document.body.appendChild(script); + }) + .catch(function (error) { + alert('Cannot load the WebAssembly framework'); + }); +}); + +// http://localhost:9979/rtviewer/index.html?loglevel=trace&ctseries=CTSERIES&rtdose=RTDOSE&rtstruct=RTSTRUCT + diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt --- a/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt Tue Apr 28 13:52:21 2020 +0200 +++ b/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt Wed Apr 29 15:54:18 2020 +0200 @@ -61,9 +61,10 @@ ) # Declare installation files for the companion files (web scaffolding) -# please note that ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.js +# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js # (the generated JS loader for the WASM module) is handled by the `install1` -# section above +# section above: it is considered to be the binary output of +# the linker. # --------------------------------------------------------------- install( FILES diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js --- a/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js Tue Apr 28 13:52:21 2020 +0200 +++ b/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js Wed Apr 29 15:54:18 2020 +0200 @@ -3,14 +3,12 @@ const WasmModuleWrapper = function() { this._InitializeViewport = undefined; - this._LoadOrthanc = undefined; - this._LoadDicomWeb = undefined; + this._LoadFromOrthanc = undefined; }; WasmModuleWrapper.prototype.Setup = function(Module) { this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]); this._LoadFromOrthanc = Module.cwrap('LoadFromOrthanc', null, [ 'string', 'int' ]); - this._LoadFromDicomWeb = Module.cwrap('LoadFromDicomWeb', null, [ 'string', 'string', 'string', 'string', 'int' ]); }; WasmModuleWrapper.prototype.InitializeViewport = function(canvasId) { @@ -21,10 +19,6 @@ this._LoadFromOrthanc(instance, frame); }; -WasmModuleWrapper.prototype.LoadFromDicomWeb = function(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) { - this._LoadFromDicomWeb(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame); -}; - var wasmModuleWrapper = new WasmModuleWrapper(); $(document).ready(function() { diff -r ffdb82850e98 -r 27e0a00bd3e8 Samples/WebAssembly/SingleFrameViewer/index.html --- a/Samples/WebAssembly/SingleFrameViewer/index.html Tue Apr 28 13:52:21 2020 +0200 +++ b/Samples/WebAssembly/SingleFrameViewer/index.html Wed Apr 29 15:54:18 2020 +0200 @@ -1,7 +1,7 @@ - Osimis' Web Viewer + Stone of Orthanc Single Frame Viewer