# HG changeset patch # User am@osimis.io # Date 1531831422 -7200 # Node ID c887eddd48f19b1dc020c439387c2f76730a0e70 # Parent e5a9b3d0347842ebbd20a08693acc09e133e721c# Parent 106a0f9781d91b9964555b93f62d19afaa748278 merge diff -r 106a0f9781d9 -r c887eddd48f1 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,6 @@ +Platforms/Generic/CMakeLists.txt.user +Platforms/Generic/ThirdPartyDownloads/ +Platforms/Wasm/CMakeLists.txt.user +Platforms/Wasm/build/ +Platforms/Wasm/build-web/ +Platforms/Wasm/ThirdPartyDownloads/ diff -r 106a0f9781d9 -r c887eddd48f1 Applications/BasicApplication.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/BasicApplication.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,291 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "IBasicApplication.h" + +#include "../Framework/Toolbox/MessagingToolbox.h" +#include "Sdl/SdlEngine.h" + +#include +#include +#include + +namespace OrthancStone +{ + // Anonymous namespace to avoid clashes against other compilation modules + namespace + { + class LogStatusBar : public IStatusBar + { + public: + virtual void ClearMessage() + { + } + + virtual void SetMessage(const std::string& message) + { + LOG(WARNING) << message; + } + }; + } + + +#if ORTHANC_ENABLE_SDL == 1 + static void DeclareSdlCommandLineOptions(boost::program_options::options_description& options) + { + // Declare the supported parameters + boost::program_options::options_description generic("Generic options"); + generic.add_options() + ("help", "Display this help and exit") + ("verbose", "Be verbose in logs") + ("orthanc", boost::program_options::value()->default_value("http://localhost:8042/"), + "URL to the Orthanc server") + ("username", "Username for the Orthanc server") + ("password", "Password for the Orthanc server") + ("https-verify", boost::program_options::value()->default_value(true), "Check HTTPS certificates") + ; + + options.add(generic); + + boost::program_options::options_description sdl("SDL options"); + sdl.add_options() + ("width", boost::program_options::value()->default_value(1024), "Initial width of the SDL window") + ("height", boost::program_options::value()->default_value(768), "Initial height of the SDL window") + ("opengl", boost::program_options::value()->default_value(true), "Enable OpenGL in SDL") + ; + + options.add(sdl); + } + + + int IBasicApplication::ExecuteWithSdl(IBasicApplication& application, + int argc, + char* argv[]) + { + /****************************************************************** + * Initialize all the subcomponents of Orthanc Stone + ******************************************************************/ + + Orthanc::Logging::Initialize(); + Orthanc::HttpClient::InitializeOpenSsl(); + Orthanc::HttpClient::GlobalInitialize(); + SdlWindow::GlobalInitialize(); + + + /****************************************************************** + * Declare and parse the command-line options of the application + ******************************************************************/ + + boost::program_options::options_description options; + DeclareSdlCommandLineOptions(options); + application.DeclareCommandLineOptions(options); + + boost::program_options::variables_map parameters; + bool error = false; + + try + { + boost::program_options::store(boost::program_options::command_line_parser(argc, argv). + options(options).run(), parameters); + boost::program_options::notify(parameters); + } + catch (boost::program_options::error& e) + { + LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what(); + error = true; + } + + + /****************************************************************** + * Configure the application with the command-line parameters + ******************************************************************/ + + if (error || parameters.count("help")) + { + std::cout << std::endl + << "Usage: " << argv[0] << " [OPTION]..." + << std::endl + << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." + << std::endl << std::endl + << "Demonstration application of Orthanc Stone using SDL." + << std::endl; + + std::cout << options << "\n"; + return error ? -1 : 0; + } + + if (parameters.count("https-verify") && + !parameters["https-verify"].as()) + { + LOG(WARNING) << "Turning off verification of HTTPS certificates (unsafe)"; + Orthanc::HttpClient::ConfigureSsl(false, ""); + } + + if (parameters.count("verbose")) + { + Orthanc::Logging::EnableInfoLevel(true); + } + + if (!parameters.count("width") || + !parameters.count("height") || + !parameters.count("opengl")) + { + LOG(ERROR) << "Parameter \"width\", \"height\" or \"opengl\" is missing"; + return -1; + } + + int w = parameters["width"].as(); + int h = parameters["height"].as(); + if (w <= 0 || h <= 0) + { + LOG(ERROR) << "Parameters \"width\" and \"height\" must be positive"; + return -1; + } + + unsigned int width = static_cast(w); + unsigned int height = static_cast(h); + LOG(WARNING) << "Initial display size: " << width << "x" << height; + + bool opengl = parameters["opengl"].as(); + if (opengl) + { + LOG(WARNING) << "OpenGL is enabled, disable it with option \"--opengl=off\" if the application crashes"; + } + else + { + LOG(WARNING) << "OpenGL is disabled, enable it with option \"--opengl=on\" for best performance"; + } + + bool success = true; + try + { + /**************************************************************** + * Initialize the connection to the Orthanc server + ****************************************************************/ + + Orthanc::WebServiceParameters webService; + + if (parameters.count("orthanc")) + { + webService.SetUrl(parameters["orthanc"].as()); + } + + if (parameters.count("username")) + { + webService.SetUsername(parameters["username"].as()); + } + + if (parameters.count("password")) + { + webService.SetPassword(parameters["password"].as()); + } + + LOG(WARNING) << "URL to the Orthanc REST API: " << webService.GetUrl(); + + { + OrthancPlugins::OrthancHttpConnection orthanc(webService); + if (!MessagingToolbox::CheckOrthancVersion(orthanc)) + { + LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of Orthanc, please upgrade"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + + /**************************************************************** + * Initialize the application + ****************************************************************/ + + LOG(WARNING) << "Creating the widgets of the application"; + + LogStatusBar statusBar; + BasicApplicationContext context(webService); + + application.Initialize(context, statusBar, parameters); + + { + BasicApplicationContext::ViewportLocker locker(context); + locker.GetViewport().SetStatusBar(statusBar); + } + + std::string title = application.GetTitle(); + if (title.empty()) + { + title = "Stone of Orthanc"; + } + + { + /************************************************************** + * Run the application inside a SDL window + **************************************************************/ + + LOG(WARNING) << "Starting the application"; + + SdlWindow window(title.c_str(), width, height, opengl); + SdlEngine sdl(window, context); + + { + BasicApplicationContext::ViewportLocker locker(context); + locker.GetViewport().Register(sdl); // (*) + } + + context.Start(); + sdl.Run(); + + LOG(WARNING) << "Stopping the application"; + + // Don't move the "Stop()" command below out of the block, + // otherwise the application might crash, because the + // "SdlEngine" is an observer of the viewport (*) and the + // update thread started by "context.Start()" would call a + // destructed object (the "SdlEngine" is deleted with the + // lexical scope). + context.Stop(); + } + + + /**************************************************************** + * Finalize the application + ****************************************************************/ + + LOG(WARNING) << "The application has stopped"; + application.Finalize(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + success = false; + } + + + /****************************************************************** + * Finalize all the subcomponents of Orthanc Stone + ******************************************************************/ + + SdlWindow::GlobalFinalize(); + Orthanc::HttpClient::GlobalFinalize(); + Orthanc::HttpClient::FinalizeOpenSsl(); + + return (success ? 0 : -1); + } +#endif + +} diff -r 106a0f9781d9 -r c887eddd48f1 Applications/BasicApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/BasicApplication.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,68 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "BasicApplicationContext.h" + +#include + +#if ORTHANC_ENABLE_SDL == 1 +# include // Necessary to avoid undefined reference to `SDL_main' +#endif + +namespace OrthancStone +{ + class IBasicApplication : public boost::noncopyable + { + public: + virtual ~IBasicApplication() + { + } + + virtual void DeclareStartupOption(const std::string& name, const std::string& defaultValue, const std::string& helpText) = 0; + virtual void Initialize(IStatusBar& statusBar, const std::map startupOptions); + + + virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) = 0; + + virtual std::string GetTitle() const = 0; + + virtual void Initialize(BasicApplicationContext& context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) = 0; + + virtual void Finalize() = 0; + +#if ORTHANC_ENABLE_SDL == 1 + static int ExecuteWithSdl(IBasicApplication& application, + int argc, + char* argv[]); +#endif + }; + + class IBasicSdlApplication : public IBasicApplication + { + public: + + + } +} diff -r 106a0f9781d9 -r c887eddd48f1 Applications/BasicApplicationContext.cpp --- a/Applications/BasicApplicationContext.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/BasicApplicationContext.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -23,122 +23,4 @@ namespace OrthancStone { - void BasicApplicationContext::UpdateThread(BasicApplicationContext* that) - { - while (!that->stopped_) - { - { - ViewportLocker locker(*that); - locker.GetViewport().UpdateContent(); - } - - boost::this_thread::sleep(boost::posix_time::milliseconds(that->updateDelay_)); - } - } - - - BasicApplicationContext::BasicApplicationContext(Orthanc::WebServiceParameters& orthanc) : - oracle_(viewportMutex_, 4), // Use 4 threads to download - //oracle_(viewportMutex_, 1), // Disable threading to be reproducible - webService_(oracle_, orthanc), - stopped_(true), - updateDelay_(100) // By default, 100ms between each refresh of the content - { - srand(time(NULL)); - } - - - BasicApplicationContext::~BasicApplicationContext() - { - for (Interactors::iterator it = interactors_.begin(); it != interactors_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - - for (SlicedVolumes::iterator it = slicedVolumes_.begin(); it != slicedVolumes_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - - for (VolumeLoaders::iterator it = volumeLoaders_.begin(); it != volumeLoaders_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - } - - - IWidget& BasicApplicationContext::SetCentralWidget(IWidget* widget) // Takes ownership - { - viewport_.SetCentralWidget(widget); - return *widget; - } - - - ISlicedVolume& BasicApplicationContext::AddSlicedVolume(ISlicedVolume* volume) - { - if (volume == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - slicedVolumes_.push_back(volume); - return *volume; - } - } - - - IVolumeLoader& BasicApplicationContext::AddVolumeLoader(IVolumeLoader* loader) - { - if (loader == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - volumeLoaders_.push_back(loader); - return *loader; - } - } - - - IWorldSceneInteractor& BasicApplicationContext::AddInteractor(IWorldSceneInteractor* interactor) - { - if (interactor == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - interactors_.push_back(interactor); - - return *interactor; - } - - - void BasicApplicationContext::Start() - { - oracle_.Start(); - - if (viewport_.HasUpdateContent()) - { - stopped_ = false; - updateThread_ = boost::thread(UpdateThread, this); - } - } - - - void BasicApplicationContext::Stop() - { - stopped_ = true; - - if (updateThread_.joinable()) - { - updateThread_.join(); - } - - oracle_.Stop(); - } } diff -r 106a0f9781d9 -r c887eddd48f1 Applications/BasicApplicationContext.h --- a/Applications/BasicApplicationContext.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/BasicApplicationContext.h Tue Jul 17 14:43:42 2018 +0200 @@ -21,82 +21,27 @@ #pragma once +#include "../Platforms/Generic/OracleWebService.h" #include "../Framework/Viewport/WidgetViewport.h" -#include "../Framework/Volumes/ISlicedVolume.h" -#include "../Framework/Volumes/IVolumeLoader.h" -#include "../Framework/Widgets/IWorldSceneInteractor.h" -#include "../Platforms/Generic/OracleWebService.h" #include -#include namespace OrthancStone { class BasicApplicationContext : public boost::noncopyable { - private: - typedef std::list SlicedVolumes; - typedef std::list VolumeLoaders; - typedef std::list Interactors; - - static void UpdateThread(BasicApplicationContext* that); - - Oracle oracle_; - OracleWebService webService_; - boost::mutex viewportMutex_; - WidgetViewport viewport_; - SlicedVolumes slicedVolumes_; - VolumeLoaders volumeLoaders_; - Interactors interactors_; - boost::thread updateThread_; - bool stopped_; - unsigned int updateDelay_; - - public: - class ViewportLocker : public boost::noncopyable - { - private: - boost::mutex::scoped_lock lock_; - IViewport& viewport_; - - public: - ViewportLocker(BasicApplicationContext& that) : - lock_(that.viewportMutex_), - viewport_(that.viewport_) - { - } - IViewport& GetViewport() const - { - return viewport_; - } - }; - - - BasicApplicationContext(Orthanc::WebServiceParameters& orthanc); - - ~BasicApplicationContext(); - - IWidget& SetCentralWidget(IWidget* widget); // Takes ownership - - IWebService& GetWebService() + protected: + IWebService& webService_; + public: + BasicApplicationContext(IWebService& webService) + : webService_(webService) { - return webService_; } - - ISlicedVolume& AddSlicedVolume(ISlicedVolume* volume); - - IVolumeLoader& AddVolumeLoader(IVolumeLoader* loader); - - IWorldSceneInteractor& AddInteractor(IWorldSceneInteractor* interactor); - void Start(); - - void Stop(); + virtual IWebService& GetWebService() {return webService_;} +// virtual IWidget& SetCentralWidget(IWidget* widget) = 0; // Takes ownership - void SetUpdateDelay(unsigned int delay) // In milliseconds - { - updateDelay_ = delay; - } + virtual ~BasicApplicationContext() {} }; } diff -r 106a0f9781d9 -r c887eddd48f1 Applications/IBasicApplication.cpp diff -r 106a0f9781d9 -r c887eddd48f1 Applications/IBasicApplication.h --- a/Applications/IBasicApplication.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/IBasicApplication.h Tue Jul 17 14:43:42 2018 +0200 @@ -22,36 +22,34 @@ #pragma once #include "BasicApplicationContext.h" - #include - -#if ORTHANC_ENABLE_SDL == 1 -# include // Necessary to avoid undefined reference to `SDL_main' -#endif +#include "../Framework/Viewport/WidgetViewport.h" namespace OrthancStone { class IBasicApplication : public boost::noncopyable { + protected: + BasicApplicationContext* context_; + public: virtual ~IBasicApplication() { } - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) = 0; + virtual void DeclareStartupOptions(boost::program_options::options_description& options) = 0; + virtual void Initialize(BasicApplicationContext* context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) = 0; +#if ORTHANC_ENABLE_SDL==0 + virtual void InitializeWasm() {} // specific initialization when the app is running in WebAssembly. This is called after the other Initialize() +#endif virtual std::string GetTitle() const = 0; - - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) = 0; + virtual IWidget* GetCentralWidget() = 0; virtual void Finalize() = 0; -#if ORTHANC_ENABLE_SDL == 1 - static int ExecuteWithSdl(IBasicApplication& application, - int argc, - char* argv[]); -#endif }; + } diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/EmptyApplication.h --- a/Applications/Samples/EmptyApplication.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/Samples/EmptyApplication.h Tue Jul 17 14:43:42 2018 +0200 @@ -32,7 +32,7 @@ class EmptyApplication : public SampleApplicationBase { public: - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + virtual void DeclareStartupOptions(boost::program_options::options_description& options) { boost::program_options::options_description generic("Sample options"); generic.add_options() @@ -44,15 +44,14 @@ options.add(generic); } - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, + virtual void Initialize(IStatusBar& statusBar, const boost::program_options::variables_map& parameters) { int red = parameters["red"].as(); int green = parameters["green"].as(); int blue = parameters["blue"].as(); - context.SetCentralWidget(new EmptyWidget(red, green, blue)); + context_->SetCentralWidget(new EmptyWidget(red, green, blue)); } }; } diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/SampleApplicationBase.h --- a/Applications/Samples/SampleApplicationBase.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/Samples/SampleApplicationBase.h Tue Jul 17 14:43:42 2018 +0200 @@ -21,12 +21,48 @@ #pragma once -#include "../IBasicApplication.h" +//#if ORTHANC_ENABLE_SDL==1 +//#include "../../Applications/Sdl/BasicSdlApplication.h" +//#else +//#include "../../Applications/Wasm/BasicWasmApplication.h" +//#endif +#include "../../Applications/IBasicApplication.h" +#include "../../Framework/Viewport/WidgetViewport.h" +//#include "SampleApplicationContext.h" namespace OrthancStone { namespace Samples { + +//#if ORTHANC_ENABLE_SDL==1 +// class SampleSdlApplicationBase : public BasicSdlApplication { +// protected: +// public: +// virtual BasicApplicationContext& CreateApplicationContext(Orthanc::WebServiceParameters& orthanc, WidgetViewport* centralViewport) { +// context_.reset(new SampleApplicationContext(orthanc, centralViewport)); + +// return *context_; +// } +// }; + +// typedef SampleSdlApplicationBase SampleApplicationBase_; +//#else +// class SampleWasmApplicationBase : public BasicWasmApplication { +// protected: +// std::unique_ptr context_; +// public: +// virtual BasicApplicationContext& CreateApplicationContext(IWebService& orthancWebService, std::shared_ptr centralViewport) { +// context_.reset(new SampleApplicationContext(orthancWebService)); +// return *context_; +// } + +// }; + +// typedef SampleWasmApplicationBase SampleApplicationBase_; + +//#endif + class SampleApplicationBase : public IBasicApplication { public: @@ -35,13 +71,10 @@ return "Stone of Orthanc - Sample"; } - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) - { - } + virtual void CustomInitialize() {} - virtual void Finalize() - { - } }; + + } } diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/SampleList.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/SampleList.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,37 @@ +// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script + +#if ORTHANC_STONE_SAMPLE == 1 +#include "EmptyApplication.h" +typedef OrthancStone::Samples::EmptyApplication Application; + +#elif ORTHANC_STONE_SAMPLE == 2 +#include "TestPatternApplication.h" +typedef OrthancStone::Samples::TestPatternApplication Application; + +#elif ORTHANC_STONE_SAMPLE == 3 +#include "SingleFrameApplication.h" +typedef OrthancStone::Samples::SingleFrameApplication Application; + +#elif ORTHANC_STONE_SAMPLE == 4 +#include "SingleVolumeApplication.h" +typedef OrthancStone::Samples::SingleVolumeApplication Application; + +#elif ORTHANC_STONE_SAMPLE == 5 +#include "BasicPetCtFusionApplication.h" +typedef OrthancStone::Samples::BasicPetCtFusionApplication Application; + +#elif ORTHANC_STONE_SAMPLE == 6 +#include "SynchronizedSeriesApplication.h" +typedef OrthancStone::Samples::SynchronizedSeriesApplication Application; + +#elif ORTHANC_STONE_SAMPLE == 7 +#include "LayoutPetCtFusionApplication.h" +typedef OrthancStone::Samples::LayoutPetCtFusionApplication Application; + +#elif ORTHANC_STONE_SAMPLE == 8 +#include "SimpleViewerApplication.h" +typedef OrthancStone::Samples::SimpleViewerApplication Application; + +#else +#error Please set the ORTHANC_STONE_SAMPLE macro +#endif diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/SampleMainSdl.cpp --- a/Applications/Samples/SampleMainSdl.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/Samples/SampleMainSdl.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -19,44 +19,14 @@ **/ -// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script - -#if ORTHANC_STONE_SAMPLE == 1 -#include "EmptyApplication.h" -typedef OrthancStone::Samples::EmptyApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 2 -#include "TestPatternApplication.h" -typedef OrthancStone::Samples::TestPatternApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 3 -#include "SingleFrameApplication.h" -typedef OrthancStone::Samples::SingleFrameApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 4 -#include "SingleVolumeApplication.h" -typedef OrthancStone::Samples::SingleVolumeApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 5 -#include "BasicPetCtFusionApplication.h" -typedef OrthancStone::Samples::BasicPetCtFusionApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 6 -#include "SynchronizedSeriesApplication.h" -typedef OrthancStone::Samples::SynchronizedSeriesApplication Application; - -#elif ORTHANC_STONE_SAMPLE == 7 -#include "LayoutPetCtFusionApplication.h" -typedef OrthancStone::Samples::LayoutPetCtFusionApplication Application; - -#else -#error Please set the ORTHANC_STONE_SAMPLE macro -#endif - +#include "SampleList.h" +#include "../Sdl/BasicSdlApplication.h" +#include "../../Framework/Messages/MessageBroker.h" int main(int argc, char* argv[]) { - Application application; + OrthancStone::MessageBroker broker; + Application application(broker); - return OrthancStone::IBasicApplication::ExecuteWithSdl(application, argc, argv); + return OrthancStone::BasicSdlApplication::ExecuteWithSdl(broker, application, argc, argv); } diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/SampleMainWasm.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/SampleMainWasm.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,32 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "Platforms/Wasm/WasmWebService.h" +#include "Platforms/Wasm/WasmViewport.h" + +#include + +#include "SampleList.h" + + +OrthancStone::IBasicApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) { + + return new Application(broker); +} \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/SimpleViewerApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/SimpleViewerApplication.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,312 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleApplicationBase.h" + +#include "../../Framework/Layers/OrthancFrameLayerSource.h" +#include "../../Framework/Widgets/LayerWidget.h" +#include "../../Framework/Widgets/LayoutWidget.h" +#include "../../Framework/Messages/IObserver.h" +#include "../../Framework/SmartLoader.h" + +#include + +namespace OrthancStone +{ + namespace Samples + { + class SimpleViewerApplication : + public SampleApplicationBase, + public IObserver + { + private: + class Interactor : public IWorldSceneInteractor + { + private: + SimpleViewerApplication& application_; + + public: + Interactor(SimpleViewerApplication& application) : + application_(application) + { + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + MouseButton button, + double x, + double y, + IStatusBar* statusBar) + { + return NULL; + } + + virtual void MouseOver(CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) + { + if (statusBar != NULL) + { + Vector p = dynamic_cast(widget).GetSlice().MapSliceToWorldCoordinates(x, y); + + char buf[64]; + sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", + p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); + statusBar->SetMessage(buf); + } + } + + virtual void MouseWheel(WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + // int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1); + + // switch (direction) + // { + // case MouseWheelDirection_Up: + // application_.OffsetSlice(-scale); + // break; + + // case MouseWheelDirection_Down: + // application_.OffsetSlice(scale); + // break; + + // default: + // break; + // } + } + + virtual void KeyPressed(WorldSceneWidget& widget, + char key, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + switch (key) + { + case 's': + widget.SetDefaultView(); + break; + case 'n': + application_.NextImage(widget); + break; + + default: + break; + } + } + }; + + + // void OffsetSlice(int offset) + // { + // if (source_ != NULL) + // { + // int slice = static_cast(slice_) + offset; + + // if (slice < 0) + // { + // slice = 0; + // } + + // if (slice >= static_cast(source_->GetSliceCount())) + // { + // slice = source_->GetSliceCount() - 1; + // } + + // if (slice != static_cast(slice_)) + // { + // SetSlice(slice); + // } + // } + // } + + + // void SetSlice(size_t index) + // { + // if (source_ != NULL && + // index < source_->GetSliceCount()) + // { + // slice_ = index; + + //#if 1 + // widget_->SetSlice(source_->GetSlice(slice_).GetGeometry()); + //#else + // // TEST for scene extents - Rotate the axes + // double a = 15.0 / 180.0 * M_PI; + + //#if 1 + // Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); + // Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0); + //#else + // // Flip the normal + // Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); + // Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0); + //#endif + + // SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y); + // widget_->SetSlice(s); + //#endif + // } + // } + + + virtual void HandleMessage(IObservable& from, const IMessage& message) { + switch (message.GetType()) { + case MessageType_GeometryReady: + mainLayout_->SetDefaultView(); + break; + default: + VLOG("unhandled message type" << message.GetType()); + } + } + + std::unique_ptr interactor_; + LayoutWidget* mainLayout_; + LayoutWidget* thumbnailsLayout_; + LayerWidget* mainViewport_; + std::vector thumbnails_; + std::vector instances_; + unsigned int currentInstanceIndex_; + OrthancStone::WidgetViewport* wasmViewport1_; + OrthancStone::WidgetViewport* wasmViewport2_; + + IStatusBar* statusBar_; + unsigned int slice_; + std::unique_ptr smartLoader_; + + public: + SimpleViewerApplication(MessageBroker& broker) : + IObserver(broker), + mainLayout_(NULL), + currentInstanceIndex_(0), + wasmViewport1_(NULL), + wasmViewport2_(NULL), + slice_(0) + { + } + + virtual void Finalize() {} + virtual IWidget* GetCentralWidget() {return mainLayout_;} + + virtual void DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + // ("study", boost::program_options::value(), + // "Orthanc ID of the study") + ("instance1", boost::program_options::value(), + "Orthanc ID of the instances") + ("instance2", boost::program_options::value(), + "Orthanc ID of the instances") + ; + + options.add(generic); + } + + virtual void Initialize(BasicApplicationContext* context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + context_ = context; + statusBar_ = &statusBar; + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); + statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport"); + + if (parameters.count("instance1") < 1) + { + LOG(ERROR) << "The instance ID is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + if (parameters.count("instance2") < 1) + { + LOG(ERROR) << "The instance ID is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + instances_.push_back(parameters["instance1"].as()); + instances_.push_back(parameters["instance2"].as()); + + mainLayout_ = new LayoutWidget(); + mainLayout_->SetPadding(10); + mainLayout_->SetBackgroundCleared(true); + mainLayout_->SetBackgroundColor(0, 0, 0); + mainLayout_->SetHorizontal(); + + thumbnailsLayout_ = new LayoutWidget(); + thumbnailsLayout_->SetPadding(10); + thumbnailsLayout_->SetBackgroundCleared(true); + thumbnailsLayout_->SetBackgroundColor(50, 50, 50); + thumbnailsLayout_->SetVertical(); + + mainViewport_ = new LayerWidget(broker_); + thumbnails_.push_back(new LayerWidget(broker_)); + thumbnails_.push_back(new LayerWidget(broker_)); + + // hierarchy + mainLayout_->AddWidget(thumbnailsLayout_); + mainLayout_->AddWidget(mainViewport_); + thumbnailsLayout_->AddWidget(thumbnails_[0]); + thumbnailsLayout_->AddWidget(thumbnails_[1]); + + // sources + smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService())); + smartLoader_->SetImageQuality(SliceImageQuality_FullPam); + smartLoader_->RegisterObserver(*this); + + mainViewport_->AddLayer(smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0)); + thumbnails_[0]->AddLayer(smartLoader_->GetFrame(instances_[0], 0)); + thumbnails_[1]->AddLayer(smartLoader_->GetFrame(instances_[1], 0)); + + mainLayout_->SetTransmitMouseOver(true); + interactor_.reset(new Interactor(*this)); + mainViewport_->SetInteractor(*interactor_); + } + +#if ORTHANC_ENABLE_SDL==0 + virtual void InitializeWasm() { + + AttachWidgetToWasmViewport("canvas", thumbnailsLayout_); + AttachWidgetToWasmViewport("canvas2", mainViewport_); + } +#endif + + void NextImage(WorldSceneWidget& widget) { + assert(context_); + statusBar_->SetMessage("displaying next image"); + + currentInstanceIndex_ = (currentInstanceIndex_ + 1) % instances_.size(); + + mainViewport_->ReplaceLayer(0, smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0)); + + } + }; + + + } +} diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/SingleFrameApplication.h --- a/Applications/Samples/SingleFrameApplication.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/Samples/SingleFrameApplication.h Tue Jul 17 14:43:42 2018 +0200 @@ -214,7 +214,7 @@ { } - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + virtual void DeclareStartupOptions(boost::program_options::options_description& options) { boost::program_options::options_description generic("Sample options"); generic.add_options() @@ -229,8 +229,7 @@ options.add(generic); } - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, + virtual void Initialize(IStatusBar& statusBar, const boost::program_options::variables_map& parameters) { using namespace OrthancStone; @@ -250,7 +249,7 @@ #if 1 std::auto_ptr layer - (new OrthancFrameLayerSource(context.GetWebService())); + (new OrthancFrameLayerSource(context_->GetWebService())); //layer->SetImageQuality(SliceImageQuality_Jpeg50); layer->LoadFrame(instance, frame); //layer->LoadSeries("6f1b492a-e181e200-44e51840-ef8db55e-af529ab6"); @@ -271,7 +270,7 @@ // 0178023P** // Extent of the CT layer: (-35.068 -20.368) => (34.932 49.632) std::auto_ptr ct; - ct.reset(new OrthancFrameLayerSource(context.GetWebService())); + ct.reset(new OrthancFrameLayerSource(context_->GetWebService())); //ct->LoadInstance("c804a1a2-142545c9-33b32fe2-3df4cec0-a2bea6d6", 0); //ct->LoadInstance("4bd4304f-47478948-71b24af2-51f4f1bc-275b6c1b", 0); // BAD SLICE //ct->SetImageQuality(SliceImageQuality_Jpeg50); @@ -281,7 +280,7 @@ widget->AddLayer(ct.release()); std::auto_ptr pet; - pet.reset(new OrthancFrameLayerSource(context.GetWebService())); + pet.reset(new OrthancFrameLayerSource(context_->GetWebService())); //pet->LoadInstance("a1c4dc6b-255d27f0-88069875-8daed730-2f5ee5c6", 0); pet->LoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e"); pet->Register(*this); @@ -309,8 +308,8 @@ widget_ = widget.get(); widget_->SetTransmitMouseOver(true); - widget_->SetInteractor(context.AddInteractor(new Interactor(*this))); - context.SetCentralWidget(widget.release()); + widget_->SetInteractor(context_->AddInteractor(new Interactor(*this))); + context_->SetCentralWidget(widget.release()); } }; } diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/SingleVolumeApplication.h --- a/Applications/Samples/SingleVolumeApplication.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/Samples/SingleVolumeApplication.h Tue Jul 17 14:43:42 2018 +0200 @@ -89,7 +89,7 @@ public: - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + virtual void DeclareStartupOptions(boost::program_options::options_description& options) { boost::program_options::options_description generic("Sample options"); generic.add_options() @@ -108,8 +108,7 @@ options.add(generic); } - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, + virtual void Initialize(IStatusBar& statusBar, const boost::program_options::variables_map& parameters) { using namespace OrthancStone; @@ -147,8 +146,8 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - unsigned int threads = parameters["threads"].as(); - bool reverse = parameters["reverse"].as(); + //unsigned int threads = parameters["threads"].as(); + //bool reverse = parameters["reverse"].as(); std::string tmp = parameters["projection"].as(); Orthanc::Toolbox::ToLowerCase(tmp); @@ -175,7 +174,7 @@ std::auto_ptr widget(new LayerWidget); #if 0 - std::auto_ptr volume(new OrthancVolumeImage(context.GetWebService(), true)); + std::auto_ptr volume(new OrthancVolumeImage(context_->GetWebService(), true)); if (series.empty()) { volume->ScheduleLoadInstance(instance); @@ -187,8 +186,8 @@ widget->AddLayer(new VolumeImageSource(*volume)); - context.AddInteractor(new Interactor(*volume, *widget, projection, 0)); - context.AddSlicedVolume(volume.release()); + context_->AddInteractor(new Interactor(*volume, *widget, projection, 0)); + context_->AddSlicedVolume(volume.release()); { RenderStyle s; @@ -199,14 +198,14 @@ widget->SetLayerStyle(0, s); } #else - std::auto_ptr ct(new OrthancVolumeImage(context.GetWebService(), false)); + std::auto_ptr ct(new OrthancVolumeImage(context_->GetWebService(), false)); //ct->ScheduleLoadSeries("15a6f44a-ac7b88fe-19c462d9-dddd918e-b01550d8"); // 0178023P //ct->ScheduleLoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d"); //ct->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // IBA //ct->ScheduleLoadSeries("03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); // 0522c0001 TCIA ct->ScheduleLoadSeries("295e8a13-dfed1320-ba6aebb2-9a13e20f-1b3eb953"); // Captain - std::auto_ptr pet(new OrthancVolumeImage(context.GetWebService(), true)); + std::auto_ptr pet(new OrthancVolumeImage(context_->GetWebService(), true)); //pet->ScheduleLoadSeries("48d2997f-8e25cd81-dd715b64-bd79cdcc-e8fcee53"); // 0178023P //pet->ScheduleLoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e"); //pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1 @@ -216,7 +215,7 @@ pet->ScheduleLoadInstance("f080888c-0ab7528a-f7d9c28c-84980eb1-ff3b0ae6"); // Captain 1 //pet->ScheduleLoadInstance("4f78055b-6499a2c5-1e089290-394acc05-3ec781c1"); // Captain 2 - std::auto_ptr rtStruct(new StructureSetLoader(context.GetWebService())); + std::auto_ptr rtStruct(new StructureSetLoader(context_->GetWebService())); //rtStruct->ScheduleLoadInstance("c2ebc17b-6b3548db-5e5da170-b8ecab71-ea03add3"); // 0178023P //rtStruct->ScheduleLoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // IBA //rtStruct->ScheduleLoadInstance("17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA @@ -226,12 +225,12 @@ widget->AddLayer(new VolumeImageSource(*pet)); widget->AddLayer(new DicomStructureSetRendererFactory(*rtStruct)); - context.AddInteractor(new Interactor(*pet, *widget, projection, 1)); - //context.AddInteractor(new VolumeImageInteractor(*ct, *widget, projection)); + context_->AddInteractor(new Interactor(*pet, *widget, projection, 1)); + //context_->AddInteractor(new VolumeImageInteractor(*ct, *widget, projection)); - context.AddSlicedVolume(ct.release()); - context.AddSlicedVolume(pet.release()); - context.AddVolumeLoader(rtStruct.release()); + context_->AddSlicedVolume(ct.release()); + context_->AddSlicedVolume(pet.release()); + context_->AddVolumeLoader(rtStruct.release()); { RenderStyle s; @@ -263,7 +262,7 @@ statusBar.SetMessage("Use the keys \"c\" to draw circles"); widget->SetTransmitMouseOver(true); - context.SetCentralWidget(widget.release()); + context_->SetCentralWidget(widget.release()); } }; } diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/TestPatternApplication.h --- a/Applications/Samples/TestPatternApplication.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/Samples/TestPatternApplication.h Tue Jul 17 14:43:42 2018 +0200 @@ -34,7 +34,7 @@ class TestPatternApplication : public SampleApplicationBase { public: - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + virtual void DeclareStartupOptions(boost::program_options::options_description& options) { boost::program_options::options_description generic("Sample options"); generic.add_options() @@ -44,8 +44,7 @@ options.add(generic); } - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, + virtual void Initialize(IStatusBar& statusBar, const boost::program_options::variables_map& parameters) { using namespace OrthancStone; @@ -56,8 +55,8 @@ layout->AddWidget(new TestCairoWidget(parameters["animate"].as())); layout->AddWidget(new TestWorldSceneWidget(parameters["animate"].as())); - context.SetCentralWidget(layout.release()); - context.SetUpdateDelay(25); // If animation, update the content each 25ms + context_->SetCentralWidget(layout.release()); + context_->SetUpdateDelay(25); // If animation, update the content each 25ms } }; } diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/Web/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Web/index.html Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,21 @@ + + + + + + + + + + + + Wasm Samples + + + + + + + \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/Web/samples-styles.css diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/Web/simple-viewer.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Web/simple-viewer.html Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,23 @@ + + + + + + + + + + + + Simple Viewer + + + +
+ + +
+ + + + \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/Web/simple-viewer.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Web/simple-viewer.ts Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,3 @@ +/// + +InitializeWasmApplication("OrthancStoneSimpleViewer", "../../../stone-orthanc"); diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/Web/tsconfig-samples.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Web/tsconfig-samples.json Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,10 @@ +{ + "extends" : "../../../Platforms/Wasm/tsconfig-stone", + "compilerOptions": { + "sourceMap": false, + "lib" : [ + "es2017", + "dom" + ] + } +} diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/Web/tsconfig-simple-viewer.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Web/tsconfig-simple-viewer.json Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,9 @@ +{ + "extends" : "./tsconfig-samples", + "compilerOptions": { + "outFile": "../../../Platforms/Wasm/build-web/app-simple-viewer.js" + }, + "include" : [ + "simple-viewer.ts" + ] +} \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Samples/samples-library.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/samples-library.js Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,8 @@ +// this file contains the JS method you want to expose to C++ code + +// mergeInto(LibraryManager.library, { +// ScheduleRedraw: function() { +// ScheduleRedraw(); +// } +// }); + \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Sdl/BasicSdlApplication.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Sdl/BasicSdlApplication.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,301 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#if ORTHANC_ENABLE_SDL != 1 +#error this file shall be included only with the ORTHANC_ENABLE_SDL set to 1 +#endif + +#include "BasicSdlApplication.h" +#include + +#include "../../Framework/Toolbox/MessagingToolbox.h" +#include "SdlEngine.h" + +#include +#include +#include +#include + +namespace OrthancStone +{ + // Anonymous namespace to avoid clashes against other compilation modules + namespace + { + class LogStatusBar : public IStatusBar + { + public: + virtual void ClearMessage() + { + } + + virtual void SetMessage(const std::string& message) + { + LOG(WARNING) << message; + } + }; + } + + + static void DeclareSdlCommandLineOptions(boost::program_options::options_description& options) + { + // Declare the supported parameters + boost::program_options::options_description generic("Generic options"); + generic.add_options() + ("help", "Display this help and exit") + ("verbose", "Be verbose in logs") + ("orthanc", boost::program_options::value()->default_value("http://localhost:8042/"), + "URL to the Orthanc server") + ("username", "Username for the Orthanc server") + ("password", "Password for the Orthanc server") + ("https-verify", boost::program_options::value()->default_value(true), "Check HTTPS certificates") + ; + + options.add(generic); + + boost::program_options::options_description sdl("SDL options"); + sdl.add_options() + ("width", boost::program_options::value()->default_value(1024), "Initial width of the SDL window") + ("height", boost::program_options::value()->default_value(768), "Initial height of the SDL window") + ("opengl", boost::program_options::value()->default_value(true), "Enable OpenGL in SDL") + ; + + options.add(sdl); + } + + + int BasicSdlApplication::ExecuteWithSdl(MessageBroker& broker, + IBasicApplication& application, + int argc, + char* argv[]) + { + /****************************************************************** + * Initialize all the subcomponents of Orthanc Stone + ******************************************************************/ + + Orthanc::Logging::Initialize(); + Orthanc::Toolbox::InitializeOpenSsl(); + Orthanc::HttpClient::GlobalInitialize(); + SdlWindow::GlobalInitialize(); + + + /****************************************************************** + * Declare and parse the command-line options of the application + ******************************************************************/ + + boost::program_options::options_description options; + DeclareSdlCommandLineOptions(options); + application.DeclareStartupOptions(options); + + boost::program_options::variables_map parameters; + bool error = false; + + try + { + boost::program_options::store(boost::program_options::command_line_parser(argc, argv). + options(options).run(), parameters); + boost::program_options::notify(parameters); + } + catch (boost::program_options::error& e) + { + LOG(ERROR) << "Error while parsing the command-line arguments: " << e.what(); + error = true; + } + + + /****************************************************************** + * Configure the application with the command-line parameters + ******************************************************************/ + + if (error || parameters.count("help")) + { + std::cout << std::endl + << "Usage: " << argv[0] << " [OPTION]..." + << std::endl + << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." + << std::endl << std::endl + << "Demonstration application of Orthanc Stone using SDL." + << std::endl; + + std::cout << options << "\n"; + return error ? -1 : 0; + } + + if (parameters.count("https-verify") && + !parameters["https-verify"].as()) + { + LOG(WARNING) << "Turning off verification of HTTPS certificates (unsafe)"; + Orthanc::HttpClient::ConfigureSsl(false, ""); + } + + if (parameters.count("verbose")) + { + Orthanc::Logging::EnableInfoLevel(true); + } + + if (!parameters.count("width") || + !parameters.count("height") || + !parameters.count("opengl")) + { + LOG(ERROR) << "Parameter \"width\", \"height\" or \"opengl\" is missing"; + return -1; + } + + int w = parameters["width"].as(); + int h = parameters["height"].as(); + if (w <= 0 || h <= 0) + { + LOG(ERROR) << "Parameters \"width\" and \"height\" must be positive"; + return -1; + } + + unsigned int width = static_cast(w); + unsigned int height = static_cast(h); + LOG(WARNING) << "Initial display size: " << width << "x" << height; + + bool opengl = parameters["opengl"].as(); + if (opengl) + { + LOG(WARNING) << "OpenGL is enabled, disable it with option \"--opengl=off\" if the application crashes"; + } + else + { + LOG(WARNING) << "OpenGL is disabled, enable it with option \"--opengl=on\" for best performance"; + } + + bool success = true; + try + { + /**************************************************************** + * Initialize the connection to the Orthanc server + ****************************************************************/ + + Orthanc::WebServiceParameters webServiceParameters; + + if (parameters.count("orthanc")) + { + webServiceParameters.SetUrl(parameters["orthanc"].as()); + } + + if (parameters.count("username")) + { + webServiceParameters.SetUsername(parameters["username"].as()); + } + + if (parameters.count("password")) + { + webServiceParameters.SetPassword(parameters["password"].as()); + } + + LOG(WARNING) << "URL to the Orthanc REST API: " << webServiceParameters.GetUrl(); + + { + OrthancPlugins::OrthancHttpConnection orthanc(webServiceParameters); + if (!MessagingToolbox::CheckOrthancVersion(orthanc)) + { + LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of Orthanc, please upgrade"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + + /**************************************************************** + * Initialize the application + ****************************************************************/ + + LOG(WARNING) << "Creating the widgets of the application"; + + LogStatusBar statusBar; + + boost::mutex stoneGlobalMutex; + Oracle oracle(stoneGlobalMutex, 4); // use 4 threads to download content + OracleWebService webService(broker, oracle, webServiceParameters); + BasicSdlApplicationContext context(webService); + + application.Initialize(&context, statusBar, parameters); + + { + BasicSdlApplicationContext::ViewportLocker locker(context); + context.SetCentralWidget(application.GetCentralWidget()); + locker.GetViewport().SetStatusBar(statusBar); + } + + std::string title = application.GetTitle(); + if (title.empty()) + { + title = "Stone of Orthanc"; + } + + { + /************************************************************** + * Run the application inside a SDL window + **************************************************************/ + + LOG(WARNING) << "Starting the application"; + + SdlWindow window(title.c_str(), width, height, opengl); + SdlEngine sdl(window, context); + + { + BasicSdlApplicationContext::ViewportLocker locker(context); + locker.GetViewport().Register(sdl); // (*) + } + + context.Start(); + sdl.Run(); + + LOG(WARNING) << "Stopping the application"; + + // Don't move the "Stop()" command below out of the block, + // otherwise the application might crash, because the + // "SdlEngine" is an observer of the viewport (*) and the + // update thread started by "context.Start()" would call a + // destructed object (the "SdlEngine" is deleted with the + // lexical scope). + context.Stop(); + } + + + /**************************************************************** + * Finalize the application + ****************************************************************/ + + LOG(WARNING) << "The application has stopped"; + application.Finalize(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + success = false; + } + + + /****************************************************************** + * Finalize all the subcomponents of Orthanc Stone + ******************************************************************/ + + SdlWindow::GlobalFinalize(); + Orthanc::HttpClient::GlobalFinalize(); + Orthanc::Toolbox::FinalizeOpenSsl(); + + return (success ? 0 : -1); + } + +} diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Sdl/BasicSdlApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Sdl/BasicSdlApplication.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,44 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../IBasicApplication.h" + +#if ORTHANC_ENABLE_SDL != 1 +#error this file shall be included only with the ORTHANC_ENABLE_SDL set to 1 +#endif + +#include // Necessary to avoid undefined reference to `SDL_main' + +namespace OrthancStone +{ + class BasicSdlApplication + { + public: + + static int ExecuteWithSdl(MessageBroker& broker, + IBasicApplication& application, + int argc, + char* argv[]); + }; + +} diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Sdl/BasicSdlApplicationContext.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Sdl/BasicSdlApplicationContext.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,84 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "BasicSdlApplicationContext.h" + +namespace OrthancStone +{ + IWidget& BasicSdlApplicationContext::SetCentralWidget(IWidget* widget) // Takes ownership + { + centralViewport_->SetCentralWidget(widget); + return *widget; + } + + + void BasicSdlApplicationContext::UpdateThread(BasicSdlApplicationContext* that) + { + while (!that->stopped_) + { + { + ViewportLocker locker(*that); + locker.GetViewport().UpdateContent(); + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(that->updateDelay_)); + } + } + + + BasicSdlApplicationContext::BasicSdlApplicationContext(OracleWebService& webService) : // Orthanc::WebServiceParameters& orthanc, WidgetViewport* centralViewport) : + BasicApplicationContext(webService), + oracleWebService_(&webService), +// oracle_(viewportMutex_, 4), // Use 4 threads to download +// webService_(oracle_, orthanc), +// centralViewport_(centralViewport), + centralViewport_(new OrthancStone::WidgetViewport()), + stopped_(true), + updateDelay_(100) // By default, 100ms between each refresh of the content + { + srand(time(NULL)); + } + + + void BasicSdlApplicationContext::Start() + { + oracleWebService_->Start(); + + if (centralViewport_->HasUpdateContent()) + { + stopped_ = false; + updateThread_ = boost::thread(UpdateThread, this); + } + } + + + void BasicSdlApplicationContext::Stop() + { + stopped_ = true; + + if (updateThread_.joinable()) + { + updateThread_.join(); + } + + oracleWebService_->Stop(); + } +} diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Sdl/BasicSdlApplicationContext.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Sdl/BasicSdlApplicationContext.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,90 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../Framework/Viewport/WidgetViewport.h" +#include "../../Framework/Volumes/ISlicedVolume.h" +#include "../../Framework/Volumes/IVolumeLoader.h" +#include "../../Framework/Widgets/IWorldSceneInteractor.h" +#include "../../Platforms/Generic/OracleWebService.h" + +#include +#include +#include "../BasicApplicationContext.h" + +namespace OrthancStone +{ + class BasicSdlApplicationContext : public BasicApplicationContext + { + private: + + static void UpdateThread(BasicSdlApplicationContext* that); + + OracleWebService* oracleWebService_; + boost::mutex viewportMutex_; + std::unique_ptr centralViewport_; + boost::thread updateThread_; + bool stopped_; + unsigned int updateDelay_; + + public: + class ViewportLocker : public boost::noncopyable + { + private: + boost::mutex::scoped_lock lock_; + IViewport& viewport_; + + public: + ViewportLocker(BasicSdlApplicationContext& that) : + lock_(that.viewportMutex_), + viewport_(*(that.centralViewport_.get())) + { + } + + IViewport& GetViewport() const + { + return viewport_; + } + }; + + + BasicSdlApplicationContext(OracleWebService& webService); + + virtual ~BasicSdlApplicationContext() {} + + virtual IWidget& SetCentralWidget(IWidget* widget); // Takes ownership + + virtual IWebService& GetWebService() + { + return webService_; + } + + void Start(); + + void Stop(); + + void SetUpdateDelay(unsigned int delay) // In milliseconds + { + updateDelay_ = delay; + } + }; +} diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Sdl/SdlEngine.cpp --- a/Applications/Sdl/SdlEngine.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/Sdl/SdlEngine.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -29,7 +29,7 @@ namespace OrthancStone { - void SdlEngine::SetSize(BasicApplicationContext::ViewportLocker& locker, + void SdlEngine::SetSize(BasicSdlApplicationContext::ViewportLocker& locker, unsigned int width, unsigned int height) { @@ -42,7 +42,7 @@ { if (viewportChanged_) { - BasicApplicationContext::ViewportLocker locker(context_); + BasicSdlApplicationContext::ViewportLocker locker(context_); surface_.Render(locker.GetViewport()); viewportChanged_ = false; @@ -99,7 +99,7 @@ SdlEngine::SdlEngine(SdlWindow& window, - BasicApplicationContext& context) : + BasicSdlApplicationContext& context) : window_(window), context_(context), surface_(window), @@ -119,7 +119,7 @@ const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); { - BasicApplicationContext::ViewportLocker locker(context_); + BasicSdlApplicationContext::ViewportLocker locker(context_); SetSize(locker, window_.GetWidth(), window_.GetHeight()); locker.GetViewport().SetDefaultView(); } @@ -134,7 +134,7 @@ while (!stop && SDL_PollEvent(&event)) { - BasicApplicationContext::ViewportLocker locker(context_); + BasicSdlApplicationContext::ViewportLocker locker(context_); if (event.type == SDL_QUIT) { diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Sdl/SdlEngine.h --- a/Applications/Sdl/SdlEngine.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Applications/Sdl/SdlEngine.h Tue Jul 17 14:43:42 2018 +0200 @@ -24,7 +24,7 @@ #if ORTHANC_ENABLE_SDL == 1 #include "SdlCairoSurface.h" -#include "../BasicApplicationContext.h" +#include "BasicSdlApplicationContext.h" namespace OrthancStone { @@ -32,11 +32,11 @@ { private: SdlWindow& window_; - BasicApplicationContext& context_; + BasicSdlApplicationContext& context_; SdlCairoSurface surface_; bool viewportChanged_; - void SetSize(BasicApplicationContext::ViewportLocker& locker, + void SetSize(BasicSdlApplicationContext::ViewportLocker& locker, unsigned int width, unsigned int height); @@ -47,7 +47,7 @@ public: SdlEngine(SdlWindow& window, - BasicApplicationContext& context); + BasicSdlApplicationContext& context); virtual ~SdlEngine(); diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Wasm/StartupParametersBuilder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Wasm/StartupParametersBuilder.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,43 @@ +#include "StartupParametersBuilder.h" + +namespace OrthancStone +{ + void StartupParametersBuilder::Clear() { + startupParameters_.clear(); + } + + void StartupParametersBuilder::SetStartupParameter(const char* name, const char* value) { + startupParameters_.push_back(std::make_tuple(name, value)); + } + + void StartupParametersBuilder::GetStartupParameters(boost::program_options::variables_map& parameters, const boost::program_options::options_description& options) { + + const char* argv[startupParameters_.size() + 1]; + int argCounter = 0; + argv[0] = "Toto.exe"; + argCounter++; + + std::string cmdLine = ""; + for (StartupParameters::const_iterator it = startupParameters_.begin(); it != startupParameters_.end(); it++) { + char* arg = new char[128]; + snprintf(arg, 128, "--%s=%s", std::get<0>(*it).c_str(), std::get<1>(*it).c_str()); + argv[argCounter] = arg; + cmdLine = cmdLine + " --" + std::get<0>(*it) + "=" + std::get<1>(*it); + argCounter++; + } + + printf("simulated cmdLine = %s\n", cmdLine.c_str()); + + try + { + boost::program_options::store(boost::program_options::command_line_parser(argCounter, argv). + options(options).run(), parameters); + boost::program_options::notify(parameters); + } + catch (boost::program_options::error& e) + { + printf("Error while parsing the command-line arguments: %s\n", e.what()); + } + + } +} \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Applications/Wasm/StartupParametersBuilder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Wasm/StartupParametersBuilder.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,50 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + +#if ORTHANC_ENABLE_SDL == 1 +#error this file shall be included only with the ORTHANC_ENABLE_SDL set to 0 +#endif + +namespace OrthancStone +{ + // This class is used to generate boost program options from a dico. + // In a Wasm context, startup options are passed as URI arguments that + // are then passed to this class as a dico. + // This class regenerates a fake command-line and parses it to produce + // the same output as if the app was started at command-line. + class StartupParametersBuilder + { + typedef std::list> StartupParameters; + StartupParameters startupParameters_; + + public: + + void Clear(); + void SetStartupParameter(const char* name, const char* value); + void GetStartupParameters(boost::program_options::variables_map& parameters_, const boost::program_options::options_description& options); + }; + +} diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Layers/DicomStructureSetRendererFactory.h --- a/Framework/Layers/DicomStructureSetRendererFactory.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Layers/DicomStructureSetRendererFactory.h Tue Jul 17 14:43:42 2018 +0200 @@ -37,21 +37,22 @@ { LayerSourceBase::NotifyGeometryReady(); } - + virtual void NotifyGeometryError(const IVolumeLoader& loader) { LayerSourceBase::NotifyGeometryError(); } - + virtual void NotifyContentChange(const IVolumeLoader& loader) { LayerSourceBase::NotifyContentChange(); } - + StructureSetLoader& loader_; public: - DicomStructureSetRendererFactory(StructureSetLoader& loader) : + DicomStructureSetRendererFactory(MessageBroker& broker, StructureSetLoader& loader) : + LayerSourceBase(broker), loader_(loader) { loader_.Register(*this); diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Layers/ILayerSource.h --- a/Framework/Layers/ILayerSource.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Layers/ILayerSource.h Tue Jul 17 14:43:42 2018 +0200 @@ -13,7 +13,7 @@ * 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 . **/ @@ -23,47 +23,80 @@ #include "ILayerRenderer.h" #include "../Toolbox/Slice.h" +#include "../../Framework/Messages/IObservable.h" +#include "../../Framework/Messages/IMessage.h" namespace OrthancStone { - class ILayerSource : public boost::noncopyable + class ILayerSource : public IObservable { public: - class IObserver : public boost::noncopyable + struct SliceChangedMessage : public IMessage + { + const Slice& slice_; + SliceChangedMessage(const Slice& slice) + : IMessage(MessageType_SliceChanged), + slice_(slice) + { + } + }; + + struct LayerReadyMessage : public IMessage { - public: - virtual ~IObserver() + std::auto_ptr& layer_; + const CoordinateSystem3D& slice_; + bool isError_; + + LayerReadyMessage(std::auto_ptr& layer, + const CoordinateSystem3D& slice, + bool isError) // TODO Shouldn't this be separate as NotifyLayerError? + : IMessage(MessageType_LayerReady), + layer_(layer), + slice_(slice), + isError_(isError) { } + }; - // Triggered as soon as the source has enough information to - // answer to "GetExtent()" - virtual void NotifyGeometryReady(const ILayerSource& source) = 0; - - virtual void NotifyGeometryError(const ILayerSource& source) = 0; - - // Triggered if the content of several slices in the source - // volume has changed - virtual void NotifyContentChange(const ILayerSource& source) = 0; + // class IObserver : public boost::noncopyable + // { + // public: + // virtual ~IObserver() + // { + // } + + // // Triggered as soon as the source has enough information to + // // answer to "GetExtent()" + // virtual void NotifyGeometryReady(const ILayerSource& source) = 0; + + // virtual void NotifyGeometryError(const ILayerSource& source) = 0; - // Triggered if the content of some individual slice in the - // source volume has changed - virtual void NotifySliceChange(const ILayerSource& source, - const Slice& slice) = 0; - - // The layer must be deleted by the observer that releases the - // std::auto_ptr - virtual void NotifyLayerReady(std::auto_ptr& layer, - const ILayerSource& source, - const CoordinateSystem3D& slice, - bool isError) = 0; // TODO Shouldn't this be separate as NotifyLayerError? - }; + // // Triggered if the content of several slices in the source + // // volume has changed + // virtual void NotifyContentChange(const ILayerSource& source) = 0; + + // // Triggered if the content of some individual slice in the + // // source volume has changed + // virtual void NotifySliceChange(const ILayerSource& source, + // const Slice& slice) = 0; + + // // The layer must be deleted by the observer that releases the + // // std::auto_ptr + // virtual void NotifyLayerReady(std::auto_ptr& layer, + // const ILayerSource& source, + // const CoordinateSystem3D& slice, + // bool isError) = 0; // TODO Shouldn't this be separate as NotifyLayerError? + // }; + ILayerSource(MessageBroker& broker) + : IObservable(broker) + {} + virtual ~ILayerSource() { } - virtual void Register(IObserver& observer) = 0; + // virtual void Register(IObserver& observer) = 0; virtual bool GetExtent(std::vector& points, const CoordinateSystem3D& viewportSlice) = 0; diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Layers/LayerSourceBase.cpp --- a/Framework/Layers/LayerSourceBase.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Layers/LayerSourceBase.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -25,63 +25,59 @@ namespace OrthancStone { - namespace - { - class LayerReadyFunctor : public boost::noncopyable - { - private: - std::auto_ptr layer_; - const CoordinateSystem3D& slice_; - bool isError_; +// namespace +// { +// class LayerReadyFunctor : public boost::noncopyable +// { +// private: +// std::auto_ptr layer_; +// const CoordinateSystem3D& slice_; +// bool isError_; - public: - LayerReadyFunctor(ILayerRenderer* layer, - const CoordinateSystem3D& slice, - bool isError) : - layer_(layer), - slice_(slice), - isError_(isError) - { - } +// public: +// LayerReadyFunctor(ILayerRenderer* layer, +// const CoordinateSystem3D& slice, +// bool isError) : +// layer_(layer), +// slice_(slice), +// isError_(isError) +// { +// } - void operator() (ILayerSource::IObserver& observer, - const ILayerSource& source) - { - observer.NotifyLayerReady(layer_, source, slice_, isError_); - } - }; - } +// void operator() (ILayerSource::IObserver& observer, +// const ILayerSource& source) +// { +// observer.NotifyLayerReady(layer_, source, slice_, isError_); +// } +// }; +// } void LayerSourceBase::NotifyGeometryReady() { - observers_.Apply(*this, &IObserver::NotifyGeometryReady); + EmitMessage(IMessage(MessageType_GeometryReady)); } void LayerSourceBase::NotifyGeometryError() { - observers_.Apply(*this, &IObserver::NotifyGeometryError); - } + EmitMessage(IMessage(MessageType_GeometryError)); + } void LayerSourceBase::NotifyContentChange() { - observers_.Apply(*this, &IObserver::NotifyContentChange); + EmitMessage(IMessage(MessageType_ContentChanged)); } void LayerSourceBase::NotifySliceChange(const Slice& slice) { - observers_.Apply(*this, &IObserver::NotifySliceChange, slice); + EmitMessage(ILayerSource::SliceChangedMessage(slice)); } void LayerSourceBase::NotifyLayerReady(ILayerRenderer* layer, const CoordinateSystem3D& slice, bool isError) { - LayerReadyFunctor functor(layer, slice, isError); - observers_.Notify(*this, functor); + std::auto_ptr renderer(layer); + EmitMessage(ILayerSource::LayerReadyMessage(renderer, slice, isError)); } - void LayerSourceBase::Register(IObserver& observer) - { - observers_.Register(observer); - } } diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Layers/LayerSourceBase.h --- a/Framework/Layers/LayerSourceBase.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Layers/LayerSourceBase.h Tue Jul 17 14:43:42 2018 +0200 @@ -28,11 +28,6 @@ { class LayerSourceBase : public ILayerSource { - private: - typedef ObserversRegistry Observers; - - Observers observers_; - protected: void NotifyGeometryReady(); @@ -46,7 +41,9 @@ const CoordinateSystem3D& slice, bool isError); - public: - virtual void Register(IObserver& observer); + LayerSourceBase(MessageBroker& broker) + : ILayerSource(broker) + {} + }; } diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Layers/OrthancFrameLayerSource.cpp --- a/Framework/Layers/OrthancFrameLayerSource.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Layers/OrthancFrameLayerSource.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -31,7 +31,7 @@ namespace OrthancStone { - void OrthancFrameLayerSource::NotifyGeometryReady(const OrthancSlicesLoader& loader) + void OrthancFrameLayerSource::OnSliceGeometryReady(const OrthancSlicesLoader& loader) { if (loader.GetSliceCount() > 0) { @@ -43,23 +43,23 @@ } } - void OrthancFrameLayerSource::NotifyGeometryError(const OrthancSlicesLoader& loader) + void OrthancFrameLayerSource::OnSliceGeometryError(const OrthancSlicesLoader& loader) { LayerSourceBase::NotifyGeometryError(); } - void OrthancFrameLayerSource::NotifySliceImageReady(const OrthancSlicesLoader& loader, + void OrthancFrameLayerSource::OnSliceImageReady(const OrthancSlicesLoader& loader, unsigned int sliceIndex, const Slice& slice, std::auto_ptr& image, SliceImageQuality quality) { - bool isFull = (quality == SliceImageQuality_Full); + bool isFull = (quality == SliceImageQuality_FullPng || quality == SliceImageQuality_FullPam); LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(), slice, isFull), slice.GetGeometry(), false); } - void OrthancFrameLayerSource::NotifySliceImageError(const OrthancSlicesLoader& loader, + void OrthancFrameLayerSource::OnSliceImageError(const OrthancSlicesLoader& loader, unsigned int sliceIndex, const Slice& slice, SliceImageQuality quality) @@ -68,9 +68,11 @@ } - OrthancFrameLayerSource::OrthancFrameLayerSource(IWebService& orthanc) : - loader_(*this, orthanc), - quality_(SliceImageQuality_Full) + OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) : + LayerSourceBase(broker), + OrthancSlicesLoader::ISliceLoaderObserver(broker), + loader_(broker, *this, orthanc), + quality_(SliceImageQuality_FullPng) { } diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Layers/OrthancFrameLayerSource.h --- a/Framework/Layers/OrthancFrameLayerSource.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Layers/OrthancFrameLayerSource.h Tue Jul 17 14:43:42 2018 +0200 @@ -29,29 +29,29 @@ { class OrthancFrameLayerSource : public LayerSourceBase, - private OrthancSlicesLoader::ICallback + private OrthancSlicesLoader::ISliceLoaderObserver { private: OrthancSlicesLoader loader_; SliceImageQuality quality_; - virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader); + virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader); - virtual void NotifyGeometryError(const OrthancSlicesLoader& loader); + virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader); - virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader, + virtual void OnSliceImageReady(const OrthancSlicesLoader& loader, unsigned int sliceIndex, const Slice& slice, std::auto_ptr& image, SliceImageQuality quality); - virtual void NotifySliceImageError(const OrthancSlicesLoader& loader, + virtual void OnSliceImageError(const OrthancSlicesLoader& loader, unsigned int sliceIndex, const Slice& slice, SliceImageQuality quality); public: - OrthancFrameLayerSource(IWebService& orthanc); + OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc); void LoadSeries(const std::string& seriesId); diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Messages/IMessage.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/IMessage.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,42 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "MessageType.h" + +#include + +namespace OrthancStone { + + struct IMessage : public boost::noncopyable + { + MessageType messageType_; + public: + IMessage(const MessageType& messageType) + : messageType_(messageType) + {} + virtual ~IMessage() {} + + MessageType GetType() const {return messageType_;} + }; + +} diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Messages/IObservable.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/IObservable.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,62 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "MessageBroker.h" +#include + +namespace OrthancStone { + + class IObservable : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + std::set observers_; + + public: + + IObservable(MessageBroker& broker) + : broker_(broker) + { + } + virtual ~IObservable() + { + } + + void EmitMessage(const IMessage& message) + { + broker_.EmitMessage(*this, observers_, message); + } + + void RegisterObserver(IObserver& observer) + { + observers_.insert(&observer); + } + + void UnregisterObserver(IObserver& observer) + { + observers_.erase(&observer); + } + }; + +} diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Messages/IObserver.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/IObserver.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,52 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "MessageBroker.h" +#include "IMessage.h" +#include "IObservable.h" + +namespace OrthancStone { + + class IObservable; + + class IObserver : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + public: + IObserver(MessageBroker& broker) + : broker_(broker) + { + broker_.Register(*this); + } + + virtual ~IObserver() + { + broker_.Unregister(*this); + } + + virtual void HandleMessage(IObservable& from, const IMessage& message) = 0; + }; + +} diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Messages/MessageBroker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/MessageBroker.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,49 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "MessageBroker.h" + +#include +#include +#include + +#include "IObserver.h" +#include "MessageType.h" + +namespace OrthancStone { + + void MessageBroker::EmitMessage(IObservable& from, std::set observers, const IMessage& message) + { + std::vector activeObservers; + std::set_intersection(observers.begin(), + observers.end(), + activeObservers_.begin(), + activeObservers_.end(), + std::back_inserter(activeObservers) + ); + + for (std::vector::iterator observer = activeObservers.begin(); observer != activeObservers.end(); observer++) + { + (*observer)->HandleMessage(from, message); + } + } + +} diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Messages/MessageBroker.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/MessageBroker.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,62 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../StoneEnumerations.h" + +#include "boost/noncopyable.hpp" +#include +#include +#include + +namespace OrthancStone +{ + class IObserver; + class IObservable; + class IMessage; + + /* + * This is a central message broker. It keeps track of all observers and knows + * when an observer is deleted. + * This way, it can prevent an observable to send a message to a dead observer. + */ + class MessageBroker : public boost::noncopyable + { + + std::set activeObservers_; // the list of observers that are currently alive (that have not been deleted) + + public: + + void Register(IObserver& observer) + { + activeObservers_.insert(&observer); + } + + void Unregister(IObserver& observer) + { + activeObservers_.erase(&observer); + } + + void EmitMessage(IObservable& from, std::set observers, const IMessage& message); + }; + +} diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Messages/MessageType.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/MessageType.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,44 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +namespace OrthancStone { + + enum MessageType + { + MessageType_Generic, + + MessageType_GeometryReady, + MessageType_GeometryError, + MessageType_ContentChanged, + MessageType_SliceChanged, + MessageType_LayerReady, + + MessageType_SliceGeometryReady, + MessageType_SliceGeometryError, + MessageType_SliceImageReady, + MessageType_SliceImageError, + + MessageType_HttpRequestSuccess, + MessageType_HttpRequestError + + }; +} diff -r 106a0f9781d9 -r c887eddd48f1 Framework/SmartLoader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/SmartLoader.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,51 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "SmartLoader.h" +#include "Layers/OrthancFrameLayerSource.h" + +namespace OrthancStone +{ + SmartLoader::SmartLoader(MessageBroker& broker, IWebService& webService) : + IObservable(broker), + IObserver(broker), + imageQuality_(SliceImageQuality_FullPam), + webService_(webService) + {} + + void SmartLoader::HandleMessage(IObservable& from, const IMessage& message) + { + // forward messages to its own observers + IObservable::broker_.EmitMessage(from, IObservable::observers_, message); + } + + ILayerSource* SmartLoader::GetFrame(const std::string& instanceId, unsigned int frame) + { + std::auto_ptr layerSource (new OrthancFrameLayerSource(IObserver::broker_, webService_)); + layerSource->SetImageQuality(imageQuality_); + layerSource->RegisterObserver(*this); + layerSource->LoadFrame(instanceId, frame); + + return layerSource.release(); + } + + +} diff -r 106a0f9781d9 -r c887eddd48f1 Framework/SmartLoader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/SmartLoader.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,49 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "Layers/ILayerSource.h" +#include "Messages/IObservable.h" +#include "../Platforms/Generic/OracleWebService.h" + +namespace OrthancStone +{ + class SmartLoader : public IObservable, IObserver + { + SliceImageQuality imageQuality_; + IWebService& webService_; + + public: + SmartLoader(MessageBroker& broker, IWebService& webService); // TODO: add maxPreloadStorageSizeInBytes + + virtual void HandleMessage(IObservable& from, const IMessage& message); + + void PreloadStudy(const std::string studyId) {/* TODO */} + void PreloadSeries(const std::string seriesId) {/* TODO */} + + void SetImageQuality(SliceImageQuality imageQuality) { imageQuality_ = imageQuality; } + + ILayerSource* GetFrame(const std::string& instanceId, unsigned int frame); + + }; + +} diff -r 106a0f9781d9 -r c887eddd48f1 Framework/StoneEnumerations.h --- a/Framework/StoneEnumerations.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/StoneEnumerations.h Tue Jul 17 14:43:42 2018 +0200 @@ -77,10 +77,13 @@ enum SliceImageQuality { - SliceImageQuality_Full, + SliceImageQuality_FullPng, // smaller to transmit but longer to generate on Orthanc side (better choice when on low bandwidth) + SliceImageQuality_FullPam, // bigger to transmit but faster to generate on Orthanc side (better choice when on localhost or LAN) SliceImageQuality_Jpeg50, SliceImageQuality_Jpeg90, - SliceImageQuality_Jpeg95 + SliceImageQuality_Jpeg95, + + SliceImageQuality_InternalRaw // downloads the raw pixels data as they are stored in the DICOM file (internal use only) }; enum SopClassUid diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Toolbox/IWebService.h --- a/Framework/Toolbox/IWebService.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Toolbox/IWebService.h Tue Jul 17 14:43:42 2018 +0200 @@ -13,7 +13,7 @@ * 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 . **/ @@ -22,41 +22,108 @@ #pragma once #include - +#include "../../Framework/Messages/IObserver.h" #include +#include namespace OrthancStone { - class IWebService : public boost::noncopyable - { - public: - class ICallback : public boost::noncopyable + class IWebService { + protected: + MessageBroker& broker_; public: - virtual ~ICallback() - { - } + typedef std::map Headers; - virtual void NotifyError(const std::string& uri, - Orthanc::IDynamicObject* payload) = 0; + class ICallback : public IObserver + { + public: + struct HttpRequestSuccessMessage: public IMessage + { + const std::string& Uri; + const void* Answer; + size_t AnswerSize; + Orthanc::IDynamicObject* Payload; + HttpRequestSuccessMessage(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) + : IMessage(MessageType_HttpRequestSuccess), + Uri(uri), + Answer(answer), + AnswerSize(answerSize), + Payload(payload) + {} + }; + + struct HttpRequestErrorMessage: public IMessage + { + const std::string& Uri; + Orthanc::IDynamicObject* Payload; + HttpRequestErrorMessage(const std::string& uri, + Orthanc::IDynamicObject* payload) + : IMessage(MessageType_HttpRequestError), + Uri(uri), + Payload(payload) + {} + }; + + ICallback(MessageBroker& broker) + : IObserver(broker) + {} + virtual ~ICallback() + { + } - virtual void NotifySuccess(const std::string& uri, - const void* answer, - size_t answerSize, - Orthanc::IDynamicObject* payload) = 0; - }; - - virtual ~IWebService() - { - } + virtual void HandleMessage(IObservable& from, const IMessage& message) + { + switch(message.GetType()) + { + case MessageType_HttpRequestError: + { const HttpRequestErrorMessage& msg = dynamic_cast(message); + OnHttpRequestError(msg.Uri, + msg.Payload); + }; break; + + case MessageType_HttpRequestSuccess: + { + const HttpRequestSuccessMessage& msg = dynamic_cast(message); + OnHttpRequestSuccess(msg.Uri, + msg.Answer, + msg.AnswerSize, + msg.Payload); + }; break; + default: + VLOG("unhandled message type" << message.GetType()); + } + } + + virtual void OnHttpRequestError(const std::string& uri, + Orthanc::IDynamicObject* payload) = 0; - virtual void ScheduleGetRequest(ICallback& callback, - const std::string& uri, - Orthanc::IDynamicObject* payload) = 0; + virtual void OnHttpRequestSuccess(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) = 0; + }; + + IWebService(MessageBroker& broker) + : broker_(broker) + {} - virtual void SchedulePostRequest(ICallback& callback, - const std::string& uri, - const std::string& body, - Orthanc::IDynamicObject* payload) = 0; - }; + virtual ~IWebService() + { + } + + virtual void ScheduleGetRequest(ICallback& callback, + const std::string& uri, + const Headers& headers, + Orthanc::IDynamicObject* payload) = 0; + + virtual void SchedulePostRequest(ICallback& callback, + const std::string& uri, + const Headers& headers, + const std::string& body, + Orthanc::IDynamicObject* payload) = 0; + }; } diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Toolbox/OrthancSlicesLoader.cpp --- a/Framework/Toolbox/OrthancSlicesLoader.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -13,7 +13,7 @@ * 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 . **/ @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -47,10 +48,10 @@ static std::string base64_decode(const std::string &in) { std::string out; - + std::vector T(256,-1); - for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; - + for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; + int val=0, valb=-8; for (size_t i = 0; i < in.size(); i++) { unsigned char c = in[i]; @@ -78,32 +79,32 @@ const Slice* slice_; std::string instanceId_; SliceImageQuality quality_; - + Operation(Mode mode) : mode_(mode) { } - + public: Mode GetMode() const { return mode_; } - + SliceImageQuality GetQuality() const { assert(mode_ == Mode_LoadImage || mode_ == Mode_LoadRawImage); return quality_; } - + unsigned int GetSliceIndex() const { assert(mode_ == Mode_LoadImage || mode_ == Mode_LoadRawImage); return sliceIndex_; } - + const Slice& GetSlice() const { assert(mode_ == Mode_LoadImage || @@ -111,32 +112,32 @@ assert(slice_ != NULL); return *slice_; } - + unsigned int GetFrame() const { assert(mode_ == Mode_FrameGeometry); return frame_; } - + const std::string& GetInstanceId() const { assert(mode_ == Mode_FrameGeometry || mode_ == Mode_InstanceGeometry); return instanceId_; } - + static Operation* DownloadSeriesGeometry() { return new Operation(Mode_SeriesGeometry); } - + static Operation* DownloadInstanceGeometry(const std::string& instanceId) { std::auto_ptr operation(new Operation(Mode_InstanceGeometry)); operation->instanceId_ = instanceId; return operation.release(); } - + static Operation* DownloadFrameGeometry(const std::string& instanceId, unsigned int frame) { @@ -145,7 +146,7 @@ operation->frame_ = frame; return operation.release(); } - + static Operation* DownloadSliceImage(unsigned int sliceIndex, const Slice& slice, SliceImageQuality quality) @@ -156,106 +157,149 @@ tmp->quality_ = quality; return tmp.release(); } - + static Operation* DownloadSliceRawImage(unsigned int sliceIndex, const Slice& slice) { std::auto_ptr tmp(new Operation(Mode_LoadRawImage)); tmp->sliceIndex_ = sliceIndex; tmp->slice_ = &slice; - tmp->quality_ = SliceImageQuality_Full; + tmp->quality_ = SliceImageQuality_InternalRaw; return tmp.release(); } + + static Operation* DownloadDicomFile(const Slice& slice) + { + std::auto_ptr tmp(new Operation(Mode_LoadDicomFile)); + tmp->slice_ = &slice; + return tmp.release(); + } + }; - + class OrthancSlicesLoader::WebCallback : public IWebService::ICallback { private: OrthancSlicesLoader& that_; - + public: - WebCallback(OrthancSlicesLoader& that) : + WebCallback(MessageBroker& broker, OrthancSlicesLoader& that) : + IWebService::ICallback(broker), that_(that) { } - - virtual void NotifySuccess(const std::string& uri, - const void* answer, - size_t answerSize, - Orthanc::IDynamicObject* payload) + + virtual void OnHttpRequestSuccess(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) { std::auto_ptr operation(dynamic_cast(payload)); - + switch (operation->GetMode()) { - case Mode_SeriesGeometry: - that_.ParseSeriesGeometry(answer, answerSize); + case Mode_SeriesGeometry: + that_.ParseSeriesGeometry(answer, answerSize); + break; + + case Mode_InstanceGeometry: + that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize); + break; + + case Mode_FrameGeometry: + that_.ParseFrameGeometry(operation->GetInstanceId(), + operation->GetFrame(), answer, answerSize); + break; + + case Mode_LoadImage: + switch (operation->GetQuality()) + { + case SliceImageQuality_FullPng: + that_.ParseSliceImagePng(*operation, answer, answerSize); break; - - case Mode_InstanceGeometry: - that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize); - break; - - case Mode_FrameGeometry: - that_.ParseFrameGeometry(operation->GetInstanceId(), - operation->GetFrame(), answer, answerSize); + case SliceImageQuality_FullPam: + that_.ParseSliceImagePam(*operation, answer, answerSize); break; - case Mode_LoadImage: - switch (operation->GetQuality()) - { - case SliceImageQuality_Full: - that_.ParseSliceImagePng(*operation, answer, answerSize); - break; - - case SliceImageQuality_Jpeg50: - case SliceImageQuality_Jpeg90: - case SliceImageQuality_Jpeg95: - that_.ParseSliceImageJpeg(*operation, answer, answerSize); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - break; - - case Mode_LoadRawImage: - that_.ParseSliceRawImage(*operation, answer, answerSize); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - virtual void NotifyError(const std::string& uri, - Orthanc::IDynamicObject* payload) - { - std::auto_ptr operation(dynamic_cast(payload)); - LOG(ERROR) << "Cannot download " << uri; - - switch (operation->GetMode()) - { - case Mode_FrameGeometry: - case Mode_SeriesGeometry: - that_.userCallback_.NotifyGeometryError(that_); - that_.state_ = State_Error; - break; - - case Mode_LoadImage: - that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(), - operation->GetSlice(), - operation->GetQuality()); + case SliceImageQuality_Jpeg50: + case SliceImageQuality_Jpeg90: + case SliceImageQuality_Jpeg95: + that_.ParseSliceImageJpeg(*operation, answer, answerSize); break; default: throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + break; + + case Mode_LoadRawImage: + that_.ParseSliceRawImage(*operation, answer, answerSize); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - } + } + + virtual void OnHttpRequestError(const std::string& uri, + Orthanc::IDynamicObject* payload) + { + std::auto_ptr operation(dynamic_cast(payload)); + LOG(ERROR) << "Cannot download " << uri; + + switch (operation->GetMode()) + { + case Mode_FrameGeometry: + case Mode_SeriesGeometry: + that_.userCallback_.OnSliceGeometryError(that_); + that_.state_ = State_Error; + break; + + case Mode_LoadImage: + that_.userCallback_.OnSliceImageError(that_, operation->GetSliceIndex(), + operation->GetSlice(), + operation->GetQuality()); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } }; - + + void OrthancSlicesLoader::ISliceLoaderObserver::HandleMessage(IObservable& from, const IMessage& message) + { + switch (message.GetType()) + { + case MessageType_SliceGeometryReady: + OnSliceGeometryReady(dynamic_cast(from)); + break; + case MessageType_SliceGeometryError: + OnSliceGeometryError(dynamic_cast(from)); + break; + case MessageType_SliceImageReady: + { + const SliceImageReadyMessage& msg = dynamic_cast(message); + OnSliceImageReady(dynamic_cast(from), + msg.sliceIndex_, + msg.slice_, + msg.image_, + msg.effectiveQuality_); + }; break; + case MessageType_SliceImageError: + { + const SliceImageErrorMessage& msg = dynamic_cast(message); + OnSliceImageError(dynamic_cast(from), + msg.sliceIndex_, + msg.slice_, + msg.effectiveQuality_); + }; break; + default: + VLOG("unhandled message type" << message.GetType()); + } + } void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation, @@ -267,19 +311,19 @@ } else { - userCallback_.NotifySliceImageReady - (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality()); + userCallback_.OnSliceImageReady + (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality()); } } - + void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) const { - userCallback_.NotifySliceImageError - (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality()); + userCallback_.OnSliceImageError + (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality()); } - - + + void OrthancSlicesLoader::SortAndFinalizeSlices() { bool ok = false; @@ -295,21 +339,21 @@ ok = true; } } - + state_ = State_GeometryReady; - + if (ok) { LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)"; - userCallback_.NotifyGeometryReady(*this); + userCallback_.OnSliceGeometryReady(*this); } else { LOG(ERROR) << "This series is empty"; - userCallback_.NotifyGeometryError(*this); + userCallback_.OnSliceGeometryError(*this); } } - + void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer, size_t size) @@ -318,18 +362,18 @@ if (!MessagingToolbox::ParseJson(series, answer, size) || series.type() != Json::objectValue) { - userCallback_.NotifyGeometryError(*this); + userCallback_.OnSliceGeometryError(*this); return; } - + Json::Value::Members instances = series.getMemberNames(); - + slices_.Reserve(instances.size()); - + for (size_t i = 0; i < instances.size(); i++) { OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]); - + Orthanc::DicomMap dicom; MessagingToolbox::ConvertDataset(dicom, dataset); @@ -338,7 +382,7 @@ { frames = 1; } - + for (unsigned int frame = 0; frame < frames; frame++) { std::auto_ptr slice(new Slice); @@ -352,11 +396,11 @@ } } } - + SortAndFinalizeSlices(); } - - + + void OrthancSlicesLoader::ParseInstanceGeometry(const std::string& instanceId, const void* answer, size_t size) @@ -365,15 +409,15 @@ if (!MessagingToolbox::ParseJson(tags, answer, size) || tags.type() != Json::objectValue) { - userCallback_.NotifyGeometryError(*this); + userCallback_.OnSliceGeometryError(*this); return; } - + OrthancPlugins::FullOrthancDataset dataset(tags); - + Orthanc::DicomMap dicom; MessagingToolbox::ConvertDataset(dicom, dataset); - + unsigned int frames; if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) { @@ -381,7 +425,7 @@ } LOG(INFO) << "Instance " << instanceId << " contains " << frames << " frame(s)"; - + for (unsigned int frame = 0; frame < frames; frame++) { std::auto_ptr slice(new Slice); @@ -392,15 +436,15 @@ else { LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId; - userCallback_.NotifyGeometryError(*this); + userCallback_.OnSliceGeometryError(*this); return; } } - + SortAndFinalizeSlices(); } - - + + void OrthancSlicesLoader::ParseFrameGeometry(const std::string& instanceId, unsigned int frame, const void* answer, @@ -410,33 +454,74 @@ if (!MessagingToolbox::ParseJson(tags, answer, size) || tags.type() != Json::objectValue) { - userCallback_.NotifyGeometryError(*this); + userCallback_.OnSliceGeometryError(*this); return; } - + OrthancPlugins::FullOrthancDataset dataset(tags); - + state_ = State_GeometryReady; - + Orthanc::DicomMap dicom; MessagingToolbox::ConvertDataset(dicom, dataset); - + std::auto_ptr slice(new Slice); if (slice->ParseOrthancFrame(dicom, instanceId, frame)) { LOG(INFO) << "Loaded instance " << instanceId; slices_.AddSlice(slice.release()); - userCallback_.NotifyGeometryReady(*this); + userCallback_.OnSliceGeometryReady(*this); } else { LOG(WARNING) << "Skipping invalid instance " << instanceId; - userCallback_.NotifyGeometryError(*this); + userCallback_.OnSliceGeometryError(*this); } } - + + + void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation, + const void* answer, + size_t size) + { + std::auto_ptr image; + + try + { + image.reset(new Orthanc::PngReader); + dynamic_cast(*image).ReadFromMemory(answer, size); + } + catch (Orthanc::OrthancException&) + { + NotifySliceImageError(operation); + return; + } + + if (image->GetWidth() != operation.GetSlice().GetWidth() || + image->GetHeight() != operation.GetSlice().GetHeight()) + { + NotifySliceImageError(operation); + return; + } - void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation, + if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() == + Orthanc::PixelFormat_SignedGrayscale16) + { + if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16) + { + image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + } + else + { + NotifySliceImageError(operation); + return; + } + } + + NotifySliceImageSuccess(operation, image); + } + + void OrthancSlicesLoader::ParseSliceImagePam(const Operation& operation, const void* answer, size_t size) { @@ -444,8 +529,8 @@ try { - image.reset(new Orthanc::PngReader); - dynamic_cast(*image).ReadFromMemory(answer, size); + image.reset(new Orthanc::PamReader); + dynamic_cast(*image).ReadFromMemory(std::string(reinterpret_cast(answer), size)); } catch (Orthanc::OrthancException&) { @@ -459,7 +544,7 @@ NotifySliceImageError(operation); return; } - + if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() == Orthanc::PixelFormat_SignedGrayscale16) { @@ -475,9 +560,9 @@ } NotifySliceImageSuccess(operation, image); - } + } - + void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation, const void* answer, size_t size) @@ -491,7 +576,7 @@ NotifySliceImageError(operation); return; } - + Json::Value& info = encoded["Orthanc"]; if (!info.isMember("PixelData") || !info.isMember("Stretched") || @@ -504,30 +589,30 @@ NotifySliceImageError(operation); return; } - + bool isSigned = false; bool isStretched = info["Stretched"].asBool(); - + if (info.isMember("IsSigned")) { if (info["IsSigned"].type() != Json::booleanValue) { NotifySliceImageError(operation); return; - } + } else { isSigned = info["IsSigned"].asBool(); } } - + std::auto_ptr reader; - + { std::string jpeg; //Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); jpeg = base64_decode(info["PixelData"].asString()); - + try { reader.reset(new Orthanc::JpegReader); @@ -539,10 +624,10 @@ return; } } - + Orthanc::PixelFormat expectedFormat = - operation.GetSlice().GetConverter().GetExpectedPixelFormat(); - + operation.GetSlice().GetConverter().GetExpectedPixelFormat(); + if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image { if (expectedFormat != Orthanc::PixelFormat_RGB24) @@ -550,7 +635,7 @@ NotifySliceImageError(operation); return; } - + if (isSigned || isStretched) { NotifySliceImageError(operation); @@ -562,13 +647,13 @@ return; } } - + if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) { NotifySliceImageError(operation); return; } - + if (!isStretched) { if (expectedFormat != reader->GetFormat()) @@ -582,10 +667,10 @@ return; } } - + int32_t stretchLow = 0; int32_t stretchHigh = 0; - + if (!info.isMember("StretchLow") || !info.isMember("StretchHigh") || info["StretchLow"].type() != Json::intValue || @@ -594,10 +679,10 @@ NotifySliceImageError(operation); return; } - + stretchLow = info["StretchLow"].asInt(); stretchHigh = info["StretchHigh"].asInt(); - + if (stretchLow < -32768 || stretchHigh > 65535 || (stretchLow < 0 && stretchHigh > 32767)) @@ -606,29 +691,29 @@ NotifySliceImageError(operation); return; } - + // Decode a grayscale JPEG 8bpp image coming from the Web viewer std::auto_ptr image - (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false)); - + (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false)); + Orthanc::ImageProcessing::Convert(*image, *reader); reader.reset(NULL); - + float scaling = static_cast(stretchHigh - stretchLow) / 255.0f; - + if (!LinearAlgebra::IsCloseToZero(scaling)) { float offset = static_cast(stretchLow) / scaling; Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); } - + NotifySliceImageSuccess(operation, image); } - - + + class StringImage : - public Orthanc::ImageAccessor, - public boost::noncopyable + public Orthanc::ImageAccessor, + public boost::noncopyable { private: std::string buffer_; @@ -643,23 +728,22 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); } - + buffer_.swap(buffer); // The source buffer is now empty - + void* data = (buffer_.empty() ? NULL : &buffer_[0]); - + AssignWritable(format, width, height, Orthanc::GetBytesPerPixel(format) * width, data); } }; - void OrthancSlicesLoader::ParseSliceRawImage(const Operation& operation, const void* answer, size_t size) { Orthanc::GzipCompressor compressor; - + std::string raw; compressor.Uncompress(raw, answer, size); @@ -676,9 +760,9 @@ // This is the case of RT-DOSE (uint32_t values) std::auto_ptr image - (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(), - info.GetHeight(), raw)); - + (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(), + info.GetHeight(), raw)); + // TODO - Only for big endian for (unsigned int y = 0; y < image->GetHeight(); y++) { @@ -688,7 +772,7 @@ *p = le32toh(*p); } } - + NotifySliceImageSuccess(operation, image); } else if (info.GetBitsAllocated() == 16 && @@ -700,30 +784,31 @@ raw.size() == info.GetWidth() * info.GetHeight() * 2) { std::auto_ptr image - (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(), - info.GetHeight(), raw)); - + (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(), + info.GetHeight(), raw)); + // TODO - Big endian ? - + NotifySliceImageSuccess(operation, image); } else { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - + } - - - OrthancSlicesLoader::OrthancSlicesLoader(ICallback& callback, + + + OrthancSlicesLoader::OrthancSlicesLoader(MessageBroker& broker, + ISliceLoaderObserver& callback, IWebService& orthanc) : - webCallback_(new WebCallback(*this)), + webCallback_(new WebCallback(broker, *this)), userCallback_(callback), orthanc_(orthanc), state_(State_Initialization) { } - + void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId) { @@ -735,11 +820,11 @@ { state_ = State_LoadingGeometry; std::string uri = "/series/" + seriesId + "/instances-tags"; - orthanc_.ScheduleGetRequest(*webCallback_, uri, Operation::DownloadSeriesGeometry()); + orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), Operation::DownloadSeriesGeometry()); } } - - + + void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId) { if (state_ != State_Initialization) @@ -749,16 +834,16 @@ else { state_ = State_LoadingGeometry; - + // Tag "3004-000c" is "Grid Frame Offset Vector", which is // mandatory to read RT DOSE, but is too long to be returned by default std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3004-000c"; orthanc_.ScheduleGetRequest - (*webCallback_, uri, Operation::DownloadInstanceGeometry(instanceId)); + (*webCallback_, uri, IWebService::Headers(), Operation::DownloadInstanceGeometry(instanceId)); } } - + void OrthancSlicesLoader::ScheduleLoadFrame(const std::string& instanceId, unsigned int frame) { @@ -771,27 +856,27 @@ state_ = State_LoadingGeometry; std::string uri = "/instances/" + instanceId + "/tags"; orthanc_.ScheduleGetRequest - (*webCallback_, uri, Operation::DownloadFrameGeometry(instanceId, frame)); + (*webCallback_, uri, IWebService::Headers(), Operation::DownloadFrameGeometry(instanceId, frame)); } } - + bool OrthancSlicesLoader::IsGeometryReady() const { return state_ == State_GeometryReady; } - - + + size_t OrthancSlicesLoader::GetSliceCount() const { if (state_ != State_GeometryReady) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - + return slices_.GetSliceCount(); } - + const Slice& OrthancSlicesLoader::GetSlice(size_t index) const { @@ -799,11 +884,11 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - + return slices_.GetSlice(index); } - + bool OrthancSlicesLoader::LookupSlice(size_t& index, const CoordinateSystem3D& plane) const { @@ -811,76 +896,109 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - + return slices_.LookupSlice(index, plane); } - + void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice, size_t index) { - std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + + std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + + boost::lexical_cast(slice.GetFrame())); + + switch (slice.GetConverter().GetExpectedPixelFormat()) + { + case Orthanc::PixelFormat_RGB24: + uri += "/preview"; + break; + + case Orthanc::PixelFormat_Grayscale16: + uri += "/image-uint16"; + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + uri += "/image-int16"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + IWebService::Headers headers; + headers["Accept"] = "image/png"; + orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), + Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPng)); + } + + void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice, + size_t index) + { + std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + boost::lexical_cast(slice.GetFrame())); switch (slice.GetConverter().GetExpectedPixelFormat()) { - case Orthanc::PixelFormat_RGB24: - uri += "/preview"; - break; + case Orthanc::PixelFormat_RGB24: + uri += "/preview"; + break; - case Orthanc::PixelFormat_Grayscale16: - uri += "/image-uint16"; - break; + case Orthanc::PixelFormat_Grayscale16: + uri += "/image-uint16"; + break; - case Orthanc::PixelFormat_SignedGrayscale16: - uri += "/image-int16"; - break; + case Orthanc::PixelFormat_SignedGrayscale16: + uri += "/image-int16"; + break; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - orthanc_.ScheduleGetRequest(*webCallback_, uri, - Operation::DownloadSliceImage(index, slice, SliceImageQuality_Full)); + IWebService::Headers headers; + headers["Accept"] = "image/x-portable-arbitrarymap"; + orthanc_.ScheduleGetRequest(*webCallback_, uri, headers, + Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPam)); } + void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice, size_t index, SliceImageQuality quality) { unsigned int value; - + switch (quality) { - case SliceImageQuality_Jpeg50: - value = 50; - break; - - case SliceImageQuality_Jpeg90: - value = 90; - break; - - case SliceImageQuality_Jpeg95: - value = 95; - break; + case SliceImageQuality_Jpeg50: + value = 50; + break; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + case SliceImageQuality_Jpeg90: + value = 90; + break; + + case SliceImageQuality_Jpeg95: + value = 95; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } // This requires the official Web viewer plugin to be installed! - std::string uri = ("/web-viewer/instances/jpeg" + - boost::lexical_cast(value) + - "-" + slice.GetOrthancInstanceId() + "_" + + std::string uri = ("/web-viewer/instances/jpeg" + + boost::lexical_cast(value) + + "-" + slice.GetOrthancInstanceId() + "_" + boost::lexical_cast(slice.GetFrame())); - - orthanc_.ScheduleGetRequest(*webCallback_, uri, + + orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), Operation::DownloadSliceImage(index, slice, quality)); } - - - + + + void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index, SliceImageQuality quality) { @@ -888,25 +1006,28 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - + const Slice& slice = GetSlice(index); - + if (slice.HasOrthancDecoding()) { - if (quality == SliceImageQuality_Full) + switch (quality) { + case SliceImageQuality_FullPng: ScheduleSliceImagePng(slice, index); - } - else - { + break; + case SliceImageQuality_FullPam: + ScheduleSliceImagePam(slice, index); + break; + default: ScheduleSliceImageJpeg(slice, index, quality); } } else { - std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + + std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + boost::lexical_cast(slice.GetFrame()) + "/raw.gz"); - orthanc_.ScheduleGetRequest(*webCallback_, uri, + orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), Operation::DownloadSliceRawImage(index, slice)); } } diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Toolbox/OrthancSlicesLoader.h --- a/Framework/Toolbox/OrthancSlicesLoader.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.h Tue Jul 17 14:43:42 2018 +0200 @@ -32,24 +32,70 @@ class OrthancSlicesLoader : public boost::noncopyable { public: - class ICallback : public boost::noncopyable + struct SliceImageReadyMessage : public IMessage + { + unsigned int sliceIndex_; + const Slice& slice_; + std::auto_ptr& image_; + SliceImageQuality effectiveQuality_; + + SliceImageReadyMessage(unsigned int sliceIndex, + const Slice& slice, + std::auto_ptr& image, + SliceImageQuality effectiveQuality) + : IMessage(MessageType_SliceImageReady), + sliceIndex_(sliceIndex), + slice_(slice), + image_(image), + effectiveQuality_(effectiveQuality) + { + } + }; + + struct SliceImageErrorMessage : public IMessage + { + const Slice& slice_; + unsigned int sliceIndex_; + SliceImageQuality effectiveQuality_; + + SliceImageErrorMessage(unsigned int sliceIndex, + const Slice& slice, + SliceImageQuality effectiveQuality) + : IMessage(MessageType_SliceImageError), + slice_(slice), + sliceIndex_(sliceIndex), + effectiveQuality_(effectiveQuality) + { + } + }; + + public: + class ISliceLoaderObserver : public IObserver { public: - virtual ~ICallback() + + ISliceLoaderObserver(MessageBroker& broker) + : IObserver(broker) { } - virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) = 0; + virtual ~ISliceLoaderObserver() + { + } + + virtual void HandleMessage(IObservable& from, const IMessage& message); - virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) = 0; + virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader) = 0; - virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader, + virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader) = 0; + + virtual void OnSliceImageReady(const OrthancSlicesLoader& loader, unsigned int sliceIndex, const Slice& slice, std::auto_ptr& image, SliceImageQuality effectiveQuality) = 0; - virtual void NotifySliceImageError(const OrthancSlicesLoader& loader, + virtual void OnSliceImageError(const OrthancSlicesLoader& loader, unsigned int sliceIndex, const Slice& slice, SliceImageQuality quality) = 0; @@ -70,7 +116,8 @@ Mode_InstanceGeometry, Mode_FrameGeometry, Mode_LoadImage, - Mode_LoadRawImage + Mode_LoadRawImage, + Mode_LoadDicomFile }; class Operation; @@ -78,7 +125,7 @@ boost::shared_ptr webCallback_; // This is a PImpl pattern - ICallback& userCallback_; + ISliceLoaderObserver& userCallback_; IWebService& orthanc_; State state_; SlicesSorter slices_; @@ -104,6 +151,10 @@ const void* answer, size_t size); + void ParseSliceImagePam(const Operation& operation, + const void* answer, + size_t size); + void ParseSliceImageJpeg(const Operation& operation, const void* answer, size_t size); @@ -114,7 +165,10 @@ void ScheduleSliceImagePng(const Slice& slice, size_t index); - + + void ScheduleSliceImagePam(const Slice& slice, + size_t index); + void ScheduleSliceImageJpeg(const Slice& slice, size_t index, SliceImageQuality quality); @@ -122,7 +176,8 @@ void SortAndFinalizeSlices(); public: - OrthancSlicesLoader(ICallback& callback, + OrthancSlicesLoader(MessageBroker& broker, + ISliceLoaderObserver& callback, IWebService& orthanc); void ScheduleLoadSeries(const std::string& seriesId); diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Viewport/WidgetViewport.cpp --- a/Framework/Viewport/WidgetViewport.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Viewport/WidgetViewport.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -64,7 +64,7 @@ } mouseTracker_.reset(NULL); - + centralWidget_.reset(widget); centralWidget_->SetViewport(*this); diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Volumes/StructureSetLoader.cpp --- a/Framework/Volumes/StructureSetLoader.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Volumes/StructureSetLoader.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -61,14 +61,14 @@ }; - void StructureSetLoader::NotifyError(const std::string& uri, + void StructureSetLoader::OnHttpRequestError(const std::string& uri, Orthanc::IDynamicObject* payload) { // TODO } - void StructureSetLoader::NotifySuccess(const std::string& uri, + void StructureSetLoader::OnHttpRequestSuccess(const std::string& uri, const void* answer, size_t answerSize, Orthanc::IDynamicObject* payload) @@ -88,7 +88,7 @@ for (std::set::const_iterator it = instances.begin(); it != instances.end(); ++it) { - orthanc_.SchedulePostRequest(*this, "/tools/lookup", *it, + orthanc_.SchedulePostRequest(*this, "/tools/lookup", IWebService::Headers(), *it, new Operation(Operation::Type_LookupSopInstanceUid, *it)); } @@ -115,7 +115,7 @@ } const std::string& instance = lookup[0]["ID"].asString(); - orthanc_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags", + orthanc_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags", IWebService::Headers(), new Operation(Operation::Type_LoadReferencedSlice, instance)); } else @@ -145,7 +145,8 @@ } - StructureSetLoader::StructureSetLoader(IWebService& orthanc) : + StructureSetLoader::StructureSetLoader(MessageBroker& broker, IWebService& orthanc) : + IWebService::ICallback(broker), orthanc_(orthanc) { } @@ -160,7 +161,7 @@ else { const std::string uri = "/instances/" + instance + "/tags?ignore-length=3006-0050"; - orthanc_.ScheduleGetRequest(*this, uri, new Operation(Operation::Type_LoadStructureSet, instance)); + orthanc_.ScheduleGetRequest(*this, uri, IWebService::Headers(), new Operation(Operation::Type_LoadStructureSet, instance)); } } diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Volumes/StructureSetLoader.h --- a/Framework/Volumes/StructureSetLoader.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Volumes/StructureSetLoader.h Tue Jul 17 14:43:42 2018 +0200 @@ -34,10 +34,10 @@ private: class Operation; - virtual void NotifyError(const std::string& uri, + virtual void OnHttpRequestError(const std::string& uri, Orthanc::IDynamicObject* payload); - virtual void NotifySuccess(const std::string& uri, + virtual void OnHttpRequestSuccess(const std::string& uri, const void* answer, size_t answerSize, Orthanc::IDynamicObject* payload); @@ -46,7 +46,7 @@ std::auto_ptr structureSet_; public: - StructureSetLoader(IWebService& orthanc); + StructureSetLoader(MessageBroker& broker, IWebService& orthanc); void ScheduleLoadInstance(const std::string& instance); diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Widgets/EmptyWidget.cpp --- a/Framework/Widgets/EmptyWidget.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Widgets/EmptyWidget.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -13,7 +13,7 @@ * 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 . **/ @@ -26,19 +26,16 @@ namespace OrthancStone { - namespace Samples + bool EmptyWidget::Render(Orthanc::ImageAccessor& surface) { - bool EmptyWidget::Render(Orthanc::ImageAccessor& surface) - { - // Note: This call is slow - Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255); - return true; - } + // Note: This call is slow + Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255); + return true; + } - - void EmptyWidget::UpdateContent() - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } + + void EmptyWidget::UpdateContent() + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } } diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Widgets/EmptyWidget.h --- a/Framework/Widgets/EmptyWidget.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Widgets/EmptyWidget.h Tue Jul 17 14:43:42 2018 +0200 @@ -13,7 +13,7 @@ * 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 . **/ @@ -25,93 +25,90 @@ namespace OrthancStone { - namespace Samples - { - /** + /** * This is a test widget that simply fills its surface with an * uniform color. **/ - class EmptyWidget : public IWidget - { - private: - uint8_t red_; - uint8_t green_; - uint8_t blue_; + class EmptyWidget : public IWidget + { + private: + uint8_t red_; + uint8_t green_; + uint8_t blue_; - public: - EmptyWidget(uint8_t red, - uint8_t green, - uint8_t blue) : - red_(red), - green_(green), - blue_(blue) - { - } + public: + EmptyWidget(uint8_t red, + uint8_t green, + uint8_t blue) : + red_(red), + green_(green), + blue_(blue) + { + } + + virtual void SetDefaultView() + { + } - virtual void SetDefaultView() - { - } - - virtual void SetParent(OrthancStone::IWidget& widget) - { - } - - virtual void SetViewport(IViewport& viewport) - { - } + virtual void SetParent(IWidget& widget) + { + } + + virtual void SetViewport(IViewport& viewport) + { + } - virtual void NotifyChange() - { - } + virtual void NotifyChange() + { + } - virtual void SetStatusBar(IStatusBar& statusBar) - { - } + virtual void SetStatusBar(IStatusBar& statusBar) + { + } + + virtual void SetSize(unsigned int width, + unsigned int height) + { + } - virtual void SetSize(unsigned int width, - unsigned int height) - { - } - - virtual bool Render(Orthanc::ImageAccessor& surface); + virtual bool Render(Orthanc::ImageAccessor& surface); - virtual IMouseTracker* CreateMouseTracker(MouseButton button, - int x, - int y, - KeyboardModifiers modifiers) - { - return NULL; - } + virtual IMouseTracker* CreateMouseTracker(MouseButton button, + int x, + int y, + KeyboardModifiers modifiers) + { + return NULL; + } - virtual void RenderMouseOver(Orthanc::ImageAccessor& target, - int x, - int y) - { - } + virtual void RenderMouseOver(Orthanc::ImageAccessor& target, + int x, + int y) + { + } - virtual void MouseWheel(MouseWheelDirection direction, - int x, - int y, - KeyboardModifiers modifiers) - { - } + virtual void MouseWheel(MouseWheelDirection direction, + int x, + int y, + KeyboardModifiers modifiers) + { + } - virtual void KeyPressed(char key, - KeyboardModifiers modifiers) - { - } + virtual void KeyPressed(char key, + KeyboardModifiers modifiers) + { + } - virtual bool HasUpdateContent() const - { - return false; - } + virtual bool HasUpdateContent() const + { + return false; + } - virtual void UpdateContent(); + virtual void UpdateContent(); - virtual bool HasRenderMouseOver() - { - return false; - } - }; - } + virtual bool HasRenderMouseOver() + { + return false; + } + }; } diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Widgets/LayerWidget.cpp --- a/Framework/Widgets/LayerWidget.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Widgets/LayerWidget.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -13,7 +13,7 @@ * 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 . **/ @@ -55,7 +55,7 @@ countMissing_++; } } - + public: Scene(const CoordinateSystem3D& slice, double thickness, @@ -184,7 +184,7 @@ #endif cairo_set_line_width(cr, 2.0 / view.GetZoom()); - cairo_set_source_rgb(cr, 1, 1, 1); + cairo_set_source_rgb(cr, 1, 1, 1); cairo_stroke_preserve(cr); cairo_set_source_rgb(cr, 1, 0, 0); cairo_fill(cr); @@ -215,7 +215,7 @@ { double z = (slice_.ProjectAlongNormal(slice.GetOrigin()) - slice_.ProjectAlongNormal(slice_.GetOrigin())); - + if (z < 0) { z = -z; @@ -249,7 +249,7 @@ return true; } } - + void LayerWidget::GetLayerExtent(Extent2D& extent, ILayerSource& source) const @@ -268,7 +268,7 @@ } } - + Extent2D LayerWidget::GetSceneExtent() { Extent2D sceneExtent; @@ -359,7 +359,8 @@ } - LayerWidget::LayerWidget() : + LayerWidget::LayerWidget(MessageBroker& broker) : + IObserver(broker), started_(false) { SetBackgroundCleared(true); @@ -388,14 +389,36 @@ layersIndex_[layer] = index; ResetPendingScene(); - layer->Register(*this); + layer->RegisterObserver(*this); ResetChangedLayers(); return index; } - + void LayerWidget::ReplaceLayer(size_t index, ILayerSource* layer) // Takes ownership + { + if (layer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (index >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + delete layers_[index]; + layers_[index] = layer; + layersIndex_[layer] = index; + + ResetPendingScene(); + layer->RegisterObserver(*this); + + InvalidateLayer(index); + } + + const RenderStyle& LayerWidget::GetLayerStyle(size_t layer) const { if (layer >= layers_.size()) @@ -457,8 +480,35 @@ } } + void LayerWidget::HandleMessage(IObservable& from, const IMessage& message) + { + switch (message.GetType()) { + case MessageType_GeometryReady: + OnGeometryReady(dynamic_cast(from)); + break; + case MessageType_GeometryError: + LOG(ERROR) << "Cannot get geometry"; + break; + case MessageType_ContentChanged: + OnContentChanged(dynamic_cast(from)); + break; + case MessageType_SliceChanged: + OnSliceChanged(dynamic_cast(from), dynamic_cast(message).slice_); + break; + case MessageType_LayerReady: + { + const ILayerSource::LayerReadyMessage& layerReadyMessage = dynamic_cast(message); + OnLayerReady(layerReadyMessage.layer_, + dynamic_cast(from), + layerReadyMessage.slice_, + layerReadyMessage.isError_); + }; break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } - void LayerWidget::NotifyGeometryReady(const ILayerSource& source) + void LayerWidget::OnGeometryReady(const ILayerSource& source) { size_t i; if (LookupLayer(i, source)) @@ -470,13 +520,6 @@ } } - - void LayerWidget::NotifyGeometryError(const ILayerSource& source) - { - LOG(ERROR) << "Cannot get geometry"; - } - - void LayerWidget::InvalidateAllLayers() { for (size_t i = 0; i < layers_.size(); i++) @@ -503,7 +546,7 @@ } - void LayerWidget::NotifyContentChange(const ILayerSource& source) + void LayerWidget::OnContentChanged(const ILayerSource& source) { size_t index; if (LookupLayer(index, source)) @@ -513,8 +556,8 @@ } - void LayerWidget::NotifySliceChange(const ILayerSource& source, - const Slice& slice) + void LayerWidget::OnSliceChanged(const ILayerSource& source, + const Slice& slice) { if (slice.ContainsPlane(slice_)) { @@ -527,10 +570,10 @@ } - void LayerWidget::NotifyLayerReady(std::auto_ptr& renderer, - const ILayerSource& source, - const CoordinateSystem3D& slice, - bool isError) + void LayerWidget::OnLayerReady(std::auto_ptr& renderer, + const ILayerSource& source, + const CoordinateSystem3D& slice, + bool isError) { size_t index; if (LookupLayer(index, source)) diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Widgets/LayerWidget.h --- a/Framework/Widgets/LayerWidget.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Widgets/LayerWidget.h Tue Jul 17 14:43:42 2018 +0200 @@ -24,6 +24,7 @@ #include "WorldSceneWidget.h" #include "../Layers/ILayerSource.h" #include "../Toolbox/Extent2D.h" +#include "../../Framework/Messages/IObserver.h" #include @@ -31,7 +32,7 @@ { class LayerWidget : public WorldSceneWidget, - private ILayerSource::IObserver + public IObserver { private: class Scene; @@ -53,23 +54,26 @@ void GetLayerExtent(Extent2D& extent, ILayerSource& source) const; - virtual void NotifyGeometryReady(const ILayerSource& source); - - virtual void NotifyGeometryError(const ILayerSource& source); + void OnGeometryReady(const ILayerSource& source); - virtual void NotifyContentChange(const ILayerSource& source); + virtual void OnContentChanged(const ILayerSource& source); - virtual void NotifySliceChange(const ILayerSource& source, + virtual void OnSliceChanged(const ILayerSource& source, const Slice& slice); - virtual void NotifyLayerReady(std::auto_ptr& renderer, + virtual void OnLayerReady(std::auto_ptr& renderer, const ILayerSource& source, const CoordinateSystem3D& slice, bool isError); + void ResetChangedLayers(); public: + LayerWidget(MessageBroker& broker); + + virtual void HandleMessage(IObservable& from, const IMessage& message); + virtual Extent2D GetSceneExtent(); protected: @@ -93,6 +97,8 @@ size_t AddLayer(ILayerSource* layer); // Takes ownership + void ReplaceLayer(size_t layerIndex, ILayerSource* layer); // Takes ownership + size_t GetLayerCount() const { return layers_.size(); diff -r 106a0f9781d9 -r c887eddd48f1 Framework/Widgets/LayoutWidget.cpp --- a/Framework/Widgets/LayoutWidget.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/Widgets/LayoutWidget.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -82,12 +82,10 @@ int top_; unsigned int width_; unsigned int height_; - bool hasUpdate_; public: ChildWidget(IWidget* widget) : - widget_(widget), - hasUpdate_(widget->HasUpdateContent()) + widget_(widget) { assert(widget != NULL); SetEmpty(); @@ -95,7 +93,7 @@ void UpdateContent() { - if (hasUpdate_) + if (widget_->HasUpdateContent()) { widget_->UpdateContent(); } diff -r 106a0f9781d9 -r c887eddd48f1 Framework/dev.h --- a/Framework/dev.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Framework/dev.h Tue Jul 17 14:43:42 2018 +0200 @@ -43,7 +43,7 @@ // TODO: Handle errors while loading class OrthancVolumeImage : public SlicedVolumeBase, - private OrthancSlicesLoader::ICallback + private OrthancSlicesLoader::ISliceLoaderObserver { private: OrthancSlicesLoader loader_; @@ -106,7 +106,7 @@ } - virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) + virtual void OnSliceGeometryReady(const OrthancSlicesLoader& loader) { if (loader.GetSliceCount() == 0) { @@ -173,13 +173,13 @@ SlicedVolumeBase::NotifyGeometryReady(); } - virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) + virtual void OnSliceGeometryError(const OrthancSlicesLoader& loader) { LOG(ERROR) << "Unable to download a volume image"; SlicedVolumeBase::NotifyGeometryError(); } - virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader, + virtual void OnSliceImageReady(const OrthancSlicesLoader& loader, unsigned int sliceIndex, const Slice& slice, std::auto_ptr& image, @@ -205,7 +205,7 @@ ScheduleSliceDownload(); } - virtual void NotifySliceImageError(const OrthancSlicesLoader& loader, + virtual void OnSliceImageError(const OrthancSlicesLoader& loader, unsigned int sliceIndex, const Slice& slice, SliceImageQuality quality) @@ -215,9 +215,11 @@ } public: - OrthancVolumeImage(IWebService& orthanc, + OrthancVolumeImage(MessageBroker& broker, + IWebService& orthanc, bool computeRange) : - loader_(*this, orthanc), + OrthancSlicesLoader::ISliceLoaderObserver(broker), + loader_(broker, *this, orthanc), computeRange_(computeRange), pendingSlices_(0) { @@ -576,7 +578,8 @@ public: - VolumeImageSource(OrthancVolumeImage& volume) : + VolumeImageSource(MessageBroker& broker, OrthancVolumeImage& volume) : + LayerSourceBase(broker), volume_(volume) { volume_.Register(*this); @@ -814,7 +817,8 @@ LayerWidget& otherPlane_; public: - SliceLocationSource(LayerWidget& otherPlane) : + SliceLocationSource(MessageBroker& broker, LayerWidget& otherPlane) : + LayerSourceBase(broker), otherPlane_(otherPlane) { NotifyGeometryReady(); diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Generic/CMakeLists.txt --- a/Platforms/Generic/CMakeLists.txt Sat Jul 14 11:20:07 2018 +0200 +++ b/Platforms/Generic/CMakeLists.txt Tue Jul 17 14:43:42 2018 +0200 @@ -45,9 +45,13 @@ ## Build all the sample applications ##################################################################### -macro(BuildSample Target Sample) +macro(BuildSample Target Header Sample) add_executable(${Target} ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainSdl.cpp +# ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationContext.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header} ${APPLICATIONS_SOURCES} ) set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) @@ -57,13 +61,14 @@ # TODO - Re-enable all these samples! -BuildSample(OrthancStoneEmpty 1) -BuildSample(OrthancStoneTestPattern 2) -BuildSample(OrthancStoneSingleFrame 3) -BuildSample(OrthancStoneSingleVolume 4) -#BuildSample(OrthancStoneBasicPetCtFusion 5) -#BuildSample(OrthancStoneSynchronizedSeries 6) -#BuildSample(OrthancStoneLayoutPetCtFusion 7) +#BuildSample(OrthancStoneEmpty EmptyApplication.h 1) +#BuildSample(OrthancStoneTestPattern TestPatternApplication.h 2) +#BuildSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) +#BuildSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) +##BuildSample(OrthancStoneBasicPetCtFusion 5) +##BuildSample(OrthancStoneSynchronizedSeries 6) +##BuildSample(OrthancStoneLayoutPetCtFusion 7) +BuildSample(OrthancStoneSimpleViewer SimpleViewerApplication.h 8) ##################################################################### @@ -72,6 +77,7 @@ add_executable(UnitTests ${GOOGLE_TEST_SOURCES} + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp ) diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Generic/OracleWebService.h --- a/Platforms/Generic/OracleWebService.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Platforms/Generic/OracleWebService.h Tue Jul 17 14:43:42 2018 +0200 @@ -35,8 +35,10 @@ Orthanc::WebServiceParameters parameters_; public: - OracleWebService(Oracle& oracle, + OracleWebService(MessageBroker& broker, + Oracle& oracle, const Orthanc::WebServiceParameters& parameters) : + IWebService(broker), oracle_(oracle), parameters_(parameters) { @@ -44,17 +46,29 @@ virtual void ScheduleGetRequest(ICallback& callback, const std::string& uri, + const Headers& headers, Orthanc::IDynamicObject* payload) { - oracle_.Submit(new WebServiceGetCommand(callback, parameters_, uri, payload)); + oracle_.Submit(new WebServiceGetCommand(broker_, callback, parameters_, uri, headers, payload)); } virtual void SchedulePostRequest(ICallback& callback, const std::string& uri, + const Headers& headers, const std::string& body, Orthanc::IDynamicObject* payload) { - oracle_.Submit(new WebServicePostCommand(callback, parameters_, uri, body, payload)); + oracle_.Submit(new WebServicePostCommand(broker_, callback, parameters_, uri, headers, body, payload)); + } + + void Start() + { + oracle_.Start(); + } + + void Stop() + { + oracle_.Stop(); } }; } diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Generic/WebServiceCommandBase.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Generic/WebServiceCommandBase.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,58 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "WebServiceCommandBase.h" + +#include + +namespace OrthancStone +{ + WebServiceCommandBase::WebServiceCommandBase(MessageBroker& broker, + IWebService::ICallback& callback, + const Orthanc::WebServiceParameters& parameters, + const std::string& uri, + const IWebService::Headers& headers, + Orthanc::IDynamicObject* payload /* takes ownership */) : + IObservable(broker), + callback_(callback), + parameters_(parameters), + uri_(uri), + headers_(headers), + payload_(payload) + { + RegisterObserver(callback); + } + + + void WebServiceCommandBase::Commit() + { + if (success_) + { + IWebService::ICallback::HttpRequestSuccessMessage message(uri_, answer_.c_str(), answer_.size(), payload_.release()); + EmitMessage(message); + } + else + { + IWebService::ICallback::HttpRequestErrorMessage message(uri_, payload_.release()); + EmitMessage(message); + } + } +} diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Generic/WebServiceCommandBase.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Generic/WebServiceCommandBase.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,58 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "IOracleCommand.h" + +#include "../../Framework/Toolbox/IWebService.h" +#include "../../Framework/Messages/IObservable.h" + +#include + +#include + +namespace OrthancStone +{ + class WebServiceCommandBase : public IOracleCommand, IObservable + { + protected: + IWebService::ICallback& callback_; + Orthanc::WebServiceParameters parameters_; + std::string uri_; + std::map headers_; + std::auto_ptr payload_; + bool success_; + std::string answer_; + + public: + WebServiceCommandBase(MessageBroker& broker, + IWebService::ICallback& callback, + const Orthanc::WebServiceParameters& parameters, + const std::string& uri, + const std::map& headers, + Orthanc::IDynamicObject* payload /* takes ownership */); + + virtual void Execute() = 0; + + virtual void Commit(); + }; +} diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Generic/WebServiceGetCommand.cpp --- a/Platforms/Generic/WebServiceGetCommand.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Platforms/Generic/WebServiceGetCommand.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -25,14 +25,13 @@ namespace OrthancStone { - WebServiceGetCommand::WebServiceGetCommand(IWebService::ICallback& callback, + WebServiceGetCommand::WebServiceGetCommand(MessageBroker& broker, + IWebService::ICallback& callback, const Orthanc::WebServiceParameters& parameters, const std::string& uri, + const IWebService::Headers& headers, Orthanc::IDynamicObject* payload /* takes ownership */) : - callback_(callback), - parameters_(parameters), - uri_(uri), - payload_(payload) + WebServiceCommandBase(broker, callback, parameters, uri, headers, payload) { } @@ -42,19 +41,13 @@ Orthanc::HttpClient client(parameters_, uri_); client.SetTimeout(60); client.SetMethod(Orthanc::HttpMethod_Get); + + for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ ) + { + client.AddHeader(it->first, it->second); + } + success_ = client.Apply(answer_); } - - void WebServiceGetCommand::Commit() - { - if (success_) - { - callback_.NotifySuccess(uri_, answer_.c_str(), answer_.size(), payload_.release()); - } - else - { - callback_.NotifyError(uri_, payload_.release()); - } - } } diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Generic/WebServiceGetCommand.h --- a/Platforms/Generic/WebServiceGetCommand.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Platforms/Generic/WebServiceGetCommand.h Tue Jul 17 14:43:42 2018 +0200 @@ -21,34 +21,20 @@ #pragma once -#include "IOracleCommand.h" - -#include "../../Framework/Toolbox/IWebService.h" - -#include - -#include +#include "WebServiceCommandBase.h" namespace OrthancStone { - class WebServiceGetCommand : public IOracleCommand + class WebServiceGetCommand : public WebServiceCommandBase { - private: - IWebService::ICallback& callback_; - Orthanc::WebServiceParameters parameters_; - std::string uri_; - std::auto_ptr payload_; - bool success_; - std::string answer_; - public: - WebServiceGetCommand(IWebService::ICallback& callback, + WebServiceGetCommand(MessageBroker& broker, + IWebService::ICallback& callback, const Orthanc::WebServiceParameters& parameters, const std::string& uri, + const IWebService::Headers& headers, Orthanc::IDynamicObject* payload /* takes ownership */); virtual void Execute(); - - virtual void Commit(); }; } diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Generic/WebServicePostCommand.cpp --- a/Platforms/Generic/WebServicePostCommand.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/Platforms/Generic/WebServicePostCommand.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -25,16 +25,15 @@ namespace OrthancStone { - WebServicePostCommand::WebServicePostCommand(IWebService::ICallback& callback, + WebServicePostCommand::WebServicePostCommand(MessageBroker& broker, + IWebService::ICallback& callback, const Orthanc::WebServiceParameters& parameters, const std::string& uri, + const IWebService::Headers& headers, const std::string& body, Orthanc::IDynamicObject* payload /* takes ownership */) : - callback_(callback), - parameters_(parameters), - uri_(uri), - body_(body), - payload_(payload) + WebServiceCommandBase(broker, callback, parameters, uri, headers, payload), + body_(body) { } @@ -44,18 +43,13 @@ client.SetTimeout(60); client.SetMethod(Orthanc::HttpMethod_Post); client.GetBody().swap(body_); + + for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ ) + { + client.AddHeader(it->first, it->second); + } + success_ = client.Apply(answer_); } - void WebServicePostCommand::Commit() - { - if (success_) - { - callback_.NotifySuccess(uri_, answer_.c_str(), answer_.size(), payload_.release()); - } - else - { - callback_.NotifyError(uri_, payload_.release()); - } - } } diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Generic/WebServicePostCommand.h --- a/Platforms/Generic/WebServicePostCommand.h Sat Jul 14 11:20:07 2018 +0200 +++ b/Platforms/Generic/WebServicePostCommand.h Tue Jul 17 14:43:42 2018 +0200 @@ -21,36 +21,24 @@ #pragma once -#include "IOracleCommand.h" - -#include "../../Framework/Toolbox/IWebService.h" - -#include - -#include +#include "WebServiceCommandBase.h" namespace OrthancStone { - class WebServicePostCommand : public IOracleCommand + class WebServicePostCommand : public WebServiceCommandBase { - private: - IWebService::ICallback& callback_; - Orthanc::WebServiceParameters parameters_; - std::string uri_; + protected: std::string body_; - std::auto_ptr payload_; - bool success_; - std::string answer_; public: - WebServicePostCommand(IWebService::ICallback& callback, + WebServicePostCommand(MessageBroker& broker, + IWebService::ICallback& callback, const Orthanc::WebServiceParameters& parameters, const std::string& uri, + const IWebService::Headers& headers, const std::string& body, Orthanc::IDynamicObject* payload /* takes ownership */); virtual void Execute(); - - virtual void Commit(); }; } diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/CMakeLists.txt Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,86 @@ +# Usage (Linux): +# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake .. + +cmake_minimum_required(VERSION 2.8.3) + + +##################################################################### +## Configuration of the Emscripten compiler for WebAssembly target +##################################################################### + +set(WASM_FLAGS "-s WASM=1") +set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") + +# Handling of memory +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") # Resize +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") # 512MB +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912 -s TOTAL_STACK=128000000") # 512MB + resize +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824") # 1GB + resize + +# To debug exceptions +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2") + + +##################################################################### +## Build a static library containing the Orthanc Stone framework +##################################################################### + +include(../../Resources/CMake/OrthancStoneParameters.cmake) + +SET(ORTHANC_SANDBOXED ON) +SET(ENABLE_SDL OFF) + +include(../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES}) + + + +# Regenerate a dummy "WasmWebService.c" file each time the "WasmWebService.js" file +# is modified, so as to force a new execution of the linking +add_custom_command( + OUTPUT "${AUTOGENERATED_DIR}/WasmWebService.c" + COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/WasmWebService.c" "" + DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmWebService.js") + +add_custom_command( + OUTPUT "${AUTOGENERATED_DIR}/default-library.c" + COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/default-library.c" "" + DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/default-library.js") + + +##################################################################### +## Build all the sample applications +##################################################################### + +include_directories(${ORTHANC_STONE_ROOT}) + + +macro(BuildSample Target Header Sample) + add_executable(${Target} + ${STONE_WASM_SOURCES} + + ${AUTOGENERATED_DIR}/WasmWebService.c + ${AUTOGENERATED_DIR}/default-library.c + + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainWasm.cpp +# ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationContext.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header} + ) + set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) + target_link_libraries(${Target} OrthancStone) +endmacro() + +#BuildSample(OrthancStoneEmpty EmptyApplication.h 1) +#BuildSample(OrthancStoneTestPattern TestPatternApplication.h 2) +#BuildSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) +#BuildSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) +#BuildSample(OrthancStoneBasicPetCtFusion 5) +#BuildSample(OrthancStoneSynchronizedSeries 6) +#BuildSample(OrthancStoneLayoutPetCtFusion 7) +BuildSample(OrthancStoneSimpleViewer SimpleViewerApplication.h 8) diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/Defaults.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/Defaults.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,259 @@ +#include "Defaults.h" + +#include "WasmWebService.h" +#include +#include "Framework/Widgets/TestCairoWidget.h" +#include +#include +#include +#include "Applications/Wasm/StartupParametersBuilder.h" + +static unsigned int width_ = 0; +static unsigned int height_ = 0; + +/**********************************/ + +static std::unique_ptr application; +static std::unique_ptr context; +static OrthancStone::StartupParametersBuilder startupParametersBuilder; +static OrthancStone::MessageBroker broker; + +static OrthancStone::ChangeObserver changeObserver_; +static OrthancStone::StatusBar statusBar_; + +static std::list> viewports_; + +std::shared_ptr FindViewportSharedPtr(ViewportHandle viewport) { + for (const auto& v : viewports_) { + if (v.get() == viewport) { + return v; + } + } + assert(false); + return std::shared_ptr(); +} + +#ifdef __cplusplus +extern "C" { +#endif + + using namespace OrthancStone; + + // when WASM needs a C++ viewport + ViewportHandle EMSCRIPTEN_KEEPALIVE CreateCppViewport() { + + std::shared_ptr viewport(new OrthancStone::WidgetViewport); + printf("viewport %x\n", viewport.get()); + + viewports_.push_back(viewport); + + printf("There are now %d viewports in C++\n", viewports_.size()); + + viewport->SetStatusBar(statusBar_); + viewport->Register(changeObserver_); + + return viewport.get(); + } + + // when WASM does not need a viewport anymore, it should release it + void EMSCRIPTEN_KEEPALIVE ReleaseCppViewport(ViewportHandle viewport) { + viewports_.remove_if([viewport](const std::shared_ptr& v) { return v.get() == viewport;}); + + printf("There are now %d viewports in C++\n", viewports_.size()); + } + + void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle viewport) { + + printf("CreateWasmApplication\n"); + + application.reset(CreateUserApplication(broker)); + WasmWebService::SetBroker(broker); + + startupParametersBuilder.Clear(); + } + + void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc, + const char* value) { + startupParametersBuilder.SetStartupParameter(keyc, value); + } + + void EMSCRIPTEN_KEEPALIVE StartWasmApplication() { + + printf("StartWasmApplication\n"); + + // recreate a command line from uri arguments and parse it + boost::program_options::variables_map parameters; + boost::program_options::options_description options; + application->DeclareStartupOptions(options); + startupParametersBuilder.GetStartupParameters(parameters, options); + + context.reset(new OrthancStone::BasicApplicationContext(OrthancStone::WasmWebService::GetInstance())); + application->Initialize(context.get(), statusBar_, parameters); + application->InitializeWasm(); + +// viewport->SetSize(width_, height_); + printf("StartWasmApplication - completed\n"); + } + + void EMSCRIPTEN_KEEPALIVE NotifyUpdateContent() + { + for (auto viewport : viewports_) { + // TODO Only launch the JavaScript timer if "HasUpdateContent()" + if (viewport->HasUpdateContent()) + { + viewport->UpdateContent(); + } + + } + + } + + + void EMSCRIPTEN_KEEPALIVE ViewportSetSize(ViewportHandle viewport, unsigned int width, unsigned int height) + { + width_ = width; + height_ = height; + + viewport->SetSize(width, height); + } + + int EMSCRIPTEN_KEEPALIVE ViewportRender(ViewportHandle viewport, + unsigned int width, + unsigned int height, + uint8_t* data) + { + changeObserver_.Reset(); + + //printf("ViewportRender called %dx%d\n", width, height); + if (width == 0 || + height == 0) + { + return 1; + } + + Orthanc::ImageAccessor surface; + surface.AssignWritable(Orthanc::PixelFormat_BGRA32, width, height, 4 * width, data); + + viewport->Render(surface); + + // Convert from BGRA32 memory layout (only color mode supported by + // Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32 (as + // expected by HTML5 canvas). This simply amounts to swapping the + // B and R channels. + uint8_t* p = data; + for (unsigned int y = 0; y < height; y++) { + for (unsigned int x = 0; x < width; x++) { + uint8_t tmp = p[0]; + p[0] = p[2]; + p[2] = tmp; + + p += 4; + } + } + + return 1; + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseDown(ViewportHandle viewport, + unsigned int rawButton, + int x, + int y, + unsigned int rawModifiers) + { + OrthancStone::MouseButton button; + switch (rawButton) + { + case 0: + button = OrthancStone::MouseButton_Left; + break; + + case 1: + button = OrthancStone::MouseButton_Middle; + break; + + case 2: + button = OrthancStone::MouseButton_Right; + break; + + default: + return; // Unknown button + } + + viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None /* TODO */); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseWheel(ViewportHandle viewport, + int deltaY, + int x, + int y, + int isControl) + { + if (deltaY != 0) + { + OrthancStone::MouseWheelDirection direction = (deltaY < 0 ? + OrthancStone::MouseWheelDirection_Up : + OrthancStone::MouseWheelDirection_Down); + OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; + + if (isControl != 0) + { + modifiers = OrthancStone::KeyboardModifiers_Control; + } + + viewport->MouseWheel(direction, x, y, modifiers); + } + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseMove(ViewportHandle viewport, + int x, + int y) + { + viewport->MouseMove(x, y); + } + + void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(ViewportHandle viewport, + const char* key, + bool isShiftPressed, + bool isControlPressed, + bool isAltPressed) + + { + OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; + if (isShiftPressed) { + modifiers = static_cast(modifiers + OrthancStone::KeyboardModifiers_Shift); + } + if (isControlPressed) { + modifiers = static_cast(modifiers + OrthancStone::KeyboardModifiers_Control); + } + if (isAltPressed) { + modifiers = static_cast(modifiers + OrthancStone::KeyboardModifiers_Alt); + } + printf("key pressed : %c\n", key[0]); + viewport->KeyPressed(key[0], modifiers); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseUp(ViewportHandle viewport) + { + viewport->MouseUp(); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseEnter(ViewportHandle viewport) + { + viewport->MouseEnter(); + } + + + void EMSCRIPTEN_KEEPALIVE ViewportMouseLeave(ViewportHandle viewport) + { + viewport->MouseLeave(); + } + + +#ifdef __cplusplus +} +#endif diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/Defaults.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/Defaults.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,75 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +typedef OrthancStone::WidgetViewport* ViewportHandle; // the objects exchanged between JS and C++ + +#ifdef __cplusplus +extern "C" { +#endif + + // JS methods accessible from C++ + extern void ScheduleWebViewportRedrawFromCpp(ViewportHandle cppViewportHandle); + + // C++ methods accessible from JS + extern void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle cppViewportHandle); + extern void EMSCRIPTEN_KEEPALIVE SetStartupParameter(const char* keyc, const char* value); + + +#ifdef __cplusplus +} +#endif + +extern OrthancStone::IBasicApplication* CreateUserApplication(OrthancStone::MessageBroker& broker); + +namespace OrthancStone { + + // default Ovserver to trigger Viewport redraw when something changes in the Viewport + class ChangeObserver : + public OrthancStone::IViewport::IObserver + { + private: + // Flag to avoid flooding JavaScript with redundant Redraw requests + bool isScheduled_; + + public: + ChangeObserver() : + isScheduled_(false) + { + } + + void Reset() + { + isScheduled_ = false; + } + + virtual void NotifyChange(const OrthancStone::IViewport &viewport) + { + if (!isScheduled_) + { + ScheduleWebViewportRedrawFromCpp((ViewportHandle)&viewport); // loosing constness when transmitted to Web + isScheduled_ = true; + } + } + }; + + // default status bar to log messages on the console/stdout + class StatusBar : public OrthancStone::IStatusBar + { + public: + virtual void ClearMessage() + { + } + + virtual void SetMessage(const std::string& message) + { + printf("%s\n", message.c_str()); + } + }; +} \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/WasmViewport.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmViewport.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,13 @@ +#include "WasmViewport.h" + +#include +#include + +std::vector> wasmViewports; + +void AttachWidgetToWasmViewport(const char* htmlCanvasId, OrthancStone::IWidget* centralWidget) { + std::shared_ptr viewport(CreateWasmViewportFromCpp(htmlCanvasId)); + viewport->SetCentralWidget(centralWidget); + + wasmViewports.push_back(viewport); +} \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/WasmViewport.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmViewport.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + // JS methods accessible from C++ + extern OrthancStone::WidgetViewport* CreateWasmViewportFromCpp(const char* htmlCanvasId); + +#ifdef __cplusplus +} +#endif + +extern void AttachWidgetToWasmViewport(const char* htmlCanvasId, OrthancStone::IWidget* centralWidget); diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/WasmWebService.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmWebService.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,122 @@ +#include "WasmWebService.h" +#include "json/value.h" +#include "json/writer.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + + extern void WasmWebService_ScheduleGetRequest(void* callback, + const char* uri, + const char* headersInJsonString, + void* payload); + + extern void WasmWebService_SchedulePostRequest(void* callback, + const char* uri, + const char* headersInJsonString, + const void* body, + size_t bodySize, + void* payload); + + void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifyError(void* callback, + const char* uri, + void* payload) + { + if (callback == NULL) + { + throw; + } + else + { + reinterpret_cast(callback)-> + OnHttpRequestError(uri, reinterpret_cast(payload)); + } + } + + void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifySuccess(void* callback, + const char* uri, + const void* body, + size_t bodySize, + void* payload) + { + if (callback == NULL) + { + throw; + } + else + { + reinterpret_cast(callback)-> + OnHttpRequestSuccess(uri, body, bodySize, reinterpret_cast(payload)); + } + } + + void EMSCRIPTEN_KEEPALIVE WasmWebService_SetBaseUri(const char* baseUri) + { + OrthancStone::WasmWebService::GetInstance().SetBaseUri(baseUri); + } + +#ifdef __cplusplus +} +#endif + + + +namespace OrthancStone +{ + MessageBroker* WasmWebService::broker_ = NULL; + + void WasmWebService::SetBaseUri(const std::string baseUri) + { + // Make sure the base url ends with "/" + if (baseUri.empty() || + baseUri[baseUri.size() - 1] != '/') + { + baseUri_ = baseUri + "/"; + } + else + { + baseUri_ = baseUri; + } + } + + void ToJsonString(std::string& output, const IWebService::Headers& headers) + { + Json::Value jsonHeaders; + for (IWebService::Headers::const_iterator it = headers.begin(); it != headers.end(); it++ ) + { + jsonHeaders[it->first] = it->second; + } + + Json::StreamWriterBuilder builder; + std::unique_ptr writer(builder.newStreamWriter()); + std::ostringstream outputStr; + + writer->write(jsonHeaders, &outputStr); + output = outputStr.str(); + } + + void WasmWebService::ScheduleGetRequest(ICallback& callback, + const std::string& relativeUri, + const Headers& headers, + Orthanc::IDynamicObject* payload) + { + std::string uri = baseUri_ + relativeUri; + std::string headersInJsonString; + ToJsonString(headersInJsonString, headers); + WasmWebService_ScheduleGetRequest(&callback, uri.c_str(), headersInJsonString.c_str(), payload); + } + + void WasmWebService::SchedulePostRequest(ICallback& callback, + const std::string& relativeUri, + const Headers& headers, + const std::string& body, + Orthanc::IDynamicObject* payload) + { + std::string uri = baseUri_ + relativeUri; + std::string headersInJsonString; + ToJsonString(headersInJsonString, headers); + WasmWebService_SchedulePostRequest(&callback, uri.c_str(), headersInJsonString.c_str(), + body.c_str(), body.size(), payload); + } +} diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/WasmWebService.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmWebService.h Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +namespace OrthancStone +{ + class WasmWebService : public IWebService + { + private: + std::string baseUri_; + static MessageBroker* broker_; + + // Private constructor => Singleton design pattern + WasmWebService(MessageBroker& broker) : + IWebService(broker), + baseUri_("../../") // note: this is configurable from the JS code by calling WasmWebService_SetBaseUri + { + } + + public: + static WasmWebService& GetInstance() + { + if (broker_ == NULL) + { + printf("WasmWebService::GetInstance(): broker not initialized\n"); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + static WasmWebService instance(*broker_); + return instance; + } + + static void SetBroker(MessageBroker& broker) + { + broker_ = &broker; + } + + void SetBaseUri(const std::string baseUri); + + virtual void ScheduleGetRequest(ICallback& callback, + const std::string& uri, + const Headers& headers, + Orthanc::IDynamicObject* payload); + + virtual void SchedulePostRequest(ICallback& callback, + const std::string& uri, + const Headers& headers, + const std::string& body, + Orthanc::IDynamicObject* payload); + + virtual void Start() + { + } + + virtual void Stop() + { + } + }; +} diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/WasmWebService.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/WasmWebService.js Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,59 @@ +mergeInto(LibraryManager.library, { + WasmWebService_ScheduleGetRequest: function(callback, url, headersInJsonString, payload) { + // Directly use XMLHttpRequest (no jQuery) to retrieve the raw binary data + // http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ + var xhr = new XMLHttpRequest(); + var url_ = UTF8ToString(url); + var headersInJsonString_ = UTF8ToString(headersInJsonString); + + xhr.open('GET', url_, true); + xhr.responseType = 'arraybuffer'; + var headers = JSON.parse(headersInJsonString_); + for (var key in headers) { + xhr.setRequestHeader(key, headers[key]); + } + // console.log(xhr); + xhr.onreadystatechange = function() { + if (this.readyState == XMLHttpRequest.DONE) { + if (xhr.status === 200) { + // TODO - Is "new Uint8Array()" necessary? This copies the + // answer to the WebAssembly stack, hence necessitating + // increasing the TOTAL_STACK parameter of Emscripten + WasmWebService_NotifySuccess(callback, url_, new Uint8Array(this.response), + this.response.byteLength, payload); + } else { + WasmWebService_NotifyError(callback, url_, payload); + } + } + } + + xhr.send(); + }, + + WasmWebService_SchedulePostRequest: function(callback, url, headersInJsonString, body, bodySize, payload) { + var xhr = new XMLHttpRequest(); + var url_ = UTF8ToString(url); + var headersInJsonString_ = UTF8ToString(headersInJsonString); + xhr.open('POST', url_, true); + xhr.responseType = 'arraybuffer'; + xhr.setRequestHeader('Content-type', 'application/octet-stream'); + + var headers = JSON.parse(headersInJsonString_); + for (var key in headers) { + xhr.setRequestHeader(key, headers[key]); + } + + xhr.onreadystatechange = function() { + if (this.readyState == XMLHttpRequest.DONE) { + if (xhr.status === 200) { + WasmWebService_NotifySuccess(callback, url_, new Uint8Array(this.response), + this.response.byteLength, payload); + } else { + WasmWebService_NotifyError(callback, url_, payload); + } + } + } + + xhr.send(new Uint8ClampedArray(HEAPU8.buffer, body, bodySize)); + } +}); diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/build-wasm.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/build-wasm.sh Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,15 @@ +#!/bin/bash + +currentDir=$(pwd) +wasmRootDir=$(pwd) + +mkdir -p $wasmRootDir/build +cd $wasmRootDir/build + +source ~/Downloads/emsdk/emsdk_env.sh +cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc -DALLOW_DOWNLOADS=ON .. +make -j 5 + +echo "-- building the web application -- " +cd $currentDir +./build-web.sh \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/build-web.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/build-web.sh Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,24 @@ +#!/bin/bash + +# this script currently assumes that the wasm code has been built on its side and is availabie in Wasm/build/ + +currentDir=$(pwd) +wasmRootDir=$(pwd) +samplesRootDir=$(pwd)/../../Applications/Samples/ + +outputDir=$wasmRootDir/build-web/ +mkdir -p $outputDir + +cp $samplesRootDir/Web/index.html $outputDir +cp $samplesRootDir/Web/samples-styles.css $outputDir + +cp $samplesRootDir/Web/simple-viewer.html $outputDir +tsc --allowJs --project $samplesRootDir/Web/tsconfig-simple-viewer.json +cp $currentDir/build/OrthancStoneSimpleViewer.js $outputDir +cp $currentDir/build/OrthancStoneSimpleViewer.wasm $outputDir + + +# cat ../wasm/build/wasm-app.js $currentDir/../../../orthanc-stone/Platforms/WebAssembly/defaults.js > $outputDir/app.js + +cd $currentDir + diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/default-library.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/default-library.js Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,11 @@ +// this file contains the JS method you want to expose to C++ code + +mergeInto(LibraryManager.library, { + ScheduleWebViewportRedrawFromCpp: function(cppViewportHandle) { + ScheduleWebViewportRedraw(cppViewportHandle); + }, + CreateWasmViewportFromCpp: function(htmlCanvasId) { + return CreateWasmViewport(htmlCanvasId); + } +}); + \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/stone-framework-loader.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/stone-framework-loader.ts Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,96 @@ +module Stone { + /** + * This file contains primitives to interface with WebAssembly and + * with the Stone framework. + **/ + + export declare type InitializationCallback = () => void; + + export declare var StoneFrameworkModule : any; + + //const ASSETS_FOLDER : string = "assets/lib"; + //const WASM_FILENAME : string = "orthanc-framework"; + + + export class Framework + { + private static singleton_ : Framework = null; + private static wasmModuleName_ : string = null; + + public static Configure(wasmModuleName: string) { + this.wasmModuleName_ = wasmModuleName; + } + + private constructor(verbose : boolean) + { + //this.ccall('Initialize', null, [ 'number' ], [ verbose ]); + } + + + public ccall(name: string, + returnType: string, + argTypes: Array, + argValues: Array) : any + { + return StoneFrameworkModule.ccall(name, returnType, argTypes, argValues); + } + + + public cwrap(name: string, + returnType: string, + argTypes: Array) : any + { + return StoneFrameworkModule.cwrap(name, returnType, argTypes); + } + + + public static GetInstance() : Framework + { + if (Framework.singleton_ == null) { + throw new Error('The WebAssembly module is not loaded yet'); + } else { + return Framework.singleton_; + } + } + + + public static Initialize(verbose: boolean, + callback: InitializationCallback) + { + console.log('Initializing WebAssembly Module'); + + ( window).StoneFrameworkModule = { + preRun: [ + function() { + console.log('Loading the Stone Framework using WebAssembly'); + } + ], + postRun: [ + function() { + // This function is called by ".js" wrapper once the ".wasm" + // WebAssembly module has been loaded and compiled by the + // browser + console.log('WebAssembly is ready'); + Framework.singleton_ = new Framework(verbose); + callback(); + } + ], + print: function(text : string) { + console.log(text); + }, + printErr: function(text : string) { + console.error(text); + }, + totalDependencies: 0 + }; + + // Dynamic loading of the JavaScript wrapper around WebAssembly + var script = document.createElement('script'); + script.type = 'application/javascript'; + //script.src = "orthanc-stone.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js'; + script.src = this.wasmModuleName_ + ".js";// "OrthancStoneSimpleViewer.js"; // ASSETS_FOLDER + '/' + WASM_FILENAME + '.js'; + script.async = true; + document.head.appendChild(script); + } + } +} \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/tsconfig-stone.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/tsconfig-stone.json Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,7 @@ +{ + "include" : [ + "../../../Platforms/Wasm/stone-framework-loader.ts", + "../../../Platforms/Wasm/wasm-application.ts", + "../../../Platforms/Wasm/wasm-viewport.ts" + ] +} diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/wasm-application.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/wasm-application.ts Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,122 @@ +/// +/// + +if (!('WebAssembly' in window)) { + alert('Sorry, your browser does not support WebAssembly :('); +} + +declare var StoneFrameworkModule : Stone.Framework; + +// global functions +var WasmWebService_NotifyError: Function = null; +var WasmWebService_NotifySuccess: Function = null; +var WasmWebService_SetBaseUri: Function = null; +var NotifyUpdateContent: Function = null; +var SetStartupParameter: Function = null; +var CreateWasmApplication: Function = null; +var CreateCppViewport: Function = null; +var ReleaseCppViewport: Function = null; +var StartWasmApplication: Function = null; + + +function UpdateContentThread() { + if (NotifyUpdateContent != null) { + NotifyUpdateContent(); + } + + setTimeout(UpdateContentThread, 100); // Update the viewport content every 100ms if need be +} + + +function GetUriParameters() { + var parameters = window.location.search.substr(1); + + if (parameters != null && + parameters != '') { + var result = {}; + var tokens = parameters.split('&'); + + for (var i = 0; i < tokens.length; i++) { + var tmp = tokens[i].split('='); + if (tmp.length == 2) { + result[tmp[0]] = decodeURIComponent(tmp[1]); + } + } + + return result; + } + else { + return {}; + } +} + +module Stone { + + export class WasmApplication { + + private viewport_: WasmViewport; + private canvasId_: string; + + private pimpl_: any; // Private pointer to the underlying WebAssembly C++ object + + public constructor(canvasId: string) { + this.canvasId_ = canvasId; + //this.module_ = module; + } + } +} + + +function _InitializeWasmApplication(canvasId: string, orthancBaseUrl: string): void { + + /************************************** */ + CreateWasmApplication(); + WasmWebService_SetBaseUri(orthancBaseUrl); + + + // parse uri and transmit the parameters to the app before initializing it + var parameters = GetUriParameters(); + + for (var key in parameters) { + if (parameters.hasOwnProperty(key)) { + SetStartupParameter(key, parameters[key]); + } + } + + StartWasmApplication(); + /************************************** */ + + UpdateContentThread(); +} + +function InitializeWasmApplication(wasmModuleName: string, orthancBaseUrl: string) { + + Stone.Framework.Configure(wasmModuleName); + + // Wait for the Orthanc Framework to be initialized (this initializes + // the WebAssembly environment) and then, create and initialize the Wasm application + Stone.Framework.Initialize(true, function () { + + console.log("Connecting C++ methods to JS methods"); + + SetStartupParameter = StoneFrameworkModule.cwrap('SetStartupParameter', null, ['string', 'string']); + CreateWasmApplication = StoneFrameworkModule.cwrap('CreateWasmApplication', null, ['number']); + CreateCppViewport = StoneFrameworkModule.cwrap('CreateCppViewport', 'number', []); + ReleaseCppViewport = StoneFrameworkModule.cwrap('ReleaseCppViewport', null, ['number']); + StartWasmApplication = StoneFrameworkModule.cwrap('StartWasmApplication', null, ['number']); + + WasmWebService_NotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']); + WasmWebService_NotifyError = StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, ['number', 'string', 'number']); + WasmWebService_SetBaseUri = StoneFrameworkModule.cwrap('WasmWebService_SetBaseUri', null, ['string']); + NotifyUpdateContent = StoneFrameworkModule.cwrap('NotifyUpdateContent', null, []); + + console.log("Connecting C++ methods to JS methods - done - 2"); + + // Prevent scrolling + document.body.addEventListener('touchmove', function (event) { + event.preventDefault(); + }, false); + + _InitializeWasmApplication("canvas", orthancBaseUrl); + }); +} \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/Wasm/wasm-viewport.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/wasm-viewport.ts Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,272 @@ +var isPendingRedraw = false; + +function ScheduleWebViewportRedraw(cppViewportHandle: any) : void +{ + if (!isPendingRedraw) { + isPendingRedraw = true; + console.log('Scheduling a refresh of the viewport, as its content changed'); + window.requestAnimationFrame(function() { + isPendingRedraw = false; + Stone.WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw(); + }); + } +} + +declare function UTF8ToString(any): string; + +function CreateWasmViewport(htmlCanvasId: string) : any { + var cppViewportHandle = CreateCppViewport(); + var canvasId = UTF8ToString(htmlCanvasId); + var webViewport = new Stone.WasmViewport(StoneFrameworkModule, canvasId, cppViewportHandle); // viewports are stored in a static map in WasmViewport -> won't be deleted + webViewport.Initialize(); + + return cppViewportHandle; +} + +module Stone { + +// export declare type InitializationCallback = () => void; + +// export declare var StoneFrameworkModule : any; + + //const ASSETS_FOLDER : string = "assets/lib"; + //const WASM_FILENAME : string = "orthanc-framework"; + + export class WasmViewport { + + private static cppWebViewportsMaps_ : Map = new Map(); + + private module_ : any; + private canvasId_ : string; + private htmlCanvas_ : HTMLCanvasElement; + private context_ : CanvasRenderingContext2D; + private imageData_ : any = null; + private renderingBuffer_ : any = null; + private touchZoom_ : any = false; + private touchTranslation_ : any = false; + + private ViewportSetSize : Function; + private ViewportRender : Function; + private ViewportMouseDown : Function; + private ViewportMouseMove : Function; + private ViewportMouseUp : Function; + private ViewportMouseEnter : Function; + private ViewportMouseLeave : Function; + private ViewportMouseWheel : Function; + private ViewportKeyPressed : Function; + + private pimpl_ : any; // Private pointer to the underlying WebAssembly C++ object + + public constructor(module: any, canvasId: string, cppViewport: any) { + + this.pimpl_ = cppViewport; + WasmViewport.cppWebViewportsMaps_[this.pimpl_] = this; + + this.module_ = module; + this.canvasId_ = canvasId; + this.htmlCanvas_ = document.getElementById(this.canvasId_) as HTMLCanvasElement; + if (this.htmlCanvas_ == null) { + console.log("Can not create WasmViewport, did not find the canvas whose id is '", this.canvasId_, "'"); + } + this.context_ = this.htmlCanvas_.getContext('2d'); + + this.ViewportSetSize = this.module_.cwrap('ViewportSetSize', null, [ 'number', 'number', 'number' ]); + this.ViewportRender = this.module_.cwrap('ViewportRender', null, [ 'number', 'number', 'number', 'number' ]); + this.ViewportMouseDown = this.module_.cwrap('ViewportMouseDown', null, [ 'number', 'number', 'number', 'number', 'number' ]); + this.ViewportMouseMove = this.module_.cwrap('ViewportMouseMove', null, [ 'number', 'number', 'number' ]); + this.ViewportMouseUp = this.module_.cwrap('ViewportMouseUp', null, [ 'number' ]); + this.ViewportMouseEnter = this.module_.cwrap('ViewportMouseEnter', null, [ 'number' ]); + this.ViewportMouseLeave = this.module_.cwrap('ViewportMouseLeave', null, [ 'number' ]); + this.ViewportMouseWheel = this.module_.cwrap('ViewportMouseWheel', null, [ 'number', 'number', 'number', 'number', 'number' ]); + this.ViewportKeyPressed = this.module_.cwrap('ViewportKeyPressed', null, [ 'number', 'string', 'number', 'number' ]); + } + + public GetCppViewport() : number { + return this.pimpl_; + } + + public static GetFromCppViewport(cppViewportHandle: number) : WasmViewport { + if (WasmViewport.cppWebViewportsMaps_[cppViewportHandle] !== undefined) { + return WasmViewport.cppWebViewportsMaps_[cppViewportHandle]; + } + console.log("WasmViewport not found !"); + return undefined; + } + + public Redraw() { + if (this.imageData_ === null || + this.renderingBuffer_ === null || + this.ViewportRender(this.pimpl_, + this.imageData_.width, + this.imageData_.height, + this.renderingBuffer_) == 0) { + console.log('The rendering has failed'); + } else { + // Create an accessor to the rendering buffer (i.e. create a + // "window" above the heap of the WASM module), then copy it to + // the ImageData object + this.imageData_.data.set(new Uint8ClampedArray( + this.module_.buffer, + this.renderingBuffer_, + this.imageData_.width * this.imageData_.height * 4)); + + this.context_.putImageData(this.imageData_, 0, 0); + } + } + + public Resize() { + if (this.imageData_ != null && + (this.imageData_.width != window.innerWidth || + this.imageData_.height != window.innerHeight)) { + this.imageData_ = null; + } + + // width/height can be defined in percent of window width/height through html attributes like data-width-ratio="50" and data-height-ratio="20" + var widthRatio = Number(this.htmlCanvas_.dataset["widthRatio"]) || 100; + var heightRatio = Number(this.htmlCanvas_.dataset["heightRatio"]) || 100; + + this.htmlCanvas_.width = window.innerWidth * (widthRatio / 100); + this.htmlCanvas_.height = window.innerHeight * (heightRatio / 100); + + console.log("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); + + if (this.imageData_ === null) { + this.imageData_ = this.context_.getImageData(0, 0, this.htmlCanvas_.width, this.htmlCanvas_.height); + this.ViewportSetSize(this.pimpl_, this.htmlCanvas_.width, this.htmlCanvas_.height); + + if (this.renderingBuffer_ != null) { + this.module_._free(this.renderingBuffer_); + } + + this.renderingBuffer_ = this.module_._malloc(this.imageData_.width * this.imageData_.height * 4); + } + + this.Redraw(); + } + + public Initialize() { + + // Force the rendering of the viewport for the first time + this.Resize(); + + var that : WasmViewport = this; + // Register an event listener to call the Resize() function + // each time the window is resized. + window.addEventListener('resize', function(event) { + that.Resize(); + }, false); + + this.htmlCanvas_.addEventListener('contextmenu', function(event) { + // Prevent right click on the canvas + event.preventDefault(); + }, false); + + this.htmlCanvas_.addEventListener('mouseleave', function(event) { + that.ViewportMouseLeave(that.pimpl_); + }); + + this.htmlCanvas_.addEventListener('mouseenter', function(event) { + that.ViewportMouseEnter(that.pimpl_); + }); + + this.htmlCanvas_.addEventListener('mousedown', function(event) { + var x = event.pageX - this.offsetLeft; + var y = event.pageY - this.offsetTop; + that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO */); + }); + + this.htmlCanvas_.addEventListener('mousemove', function(event) { + var x = event.pageX - this.offsetLeft; + var y = event.pageY - this.offsetTop; + that.ViewportMouseMove(that.pimpl_, x, y); + }); + + this.htmlCanvas_.addEventListener('mouseup', function(event) { + that.ViewportMouseUp(that.pimpl_); + }); + + window.addEventListener('keydown', function(event) { + that.ViewportKeyPressed(that.pimpl_, event.key, event.shiftKey, event.ctrlKey, event.altKey); + }); + + this.htmlCanvas_.addEventListener('wheel', function(event) { + var x = event.pageX - this.offsetLeft; + var y = event.pageY - this.offsetTop; + that.ViewportMouseWheel(that.pimpl_, event.deltaY, x, y, event.ctrlKey); + event.preventDefault(); + }); + + this.htmlCanvas_.addEventListener('touchstart', function(event) { + that.ResetTouch(); + }); + + this.htmlCanvas_.addEventListener('touchend', function(event) { + that.ResetTouch(); + }); + + this.htmlCanvas_.addEventListener('touchmove', function(event) { + if (that.touchTranslation_.length == 2) { + var t = that.GetTouchTranslation(event); + that.ViewportMouseMove(that.pimpl_, t[0], t[1]); + } + else if (that.touchZoom_.length == 3) { + var z0 = that.touchZoom_; + var z1 = that.GetTouchZoom(event); + that.ViewportMouseMove(that.pimpl_, z0[0], z0[1] - z0[2] + z1[2]); + } + else { + // Realize the gesture event + if (event.targetTouches.length == 1) { + // Exactly one finger inside the canvas => Setup a translation + that.touchTranslation_ = that.GetTouchTranslation(event); + that.ViewportMouseDown(that.pimpl_, + 1 /* middle button */, + that.touchTranslation_[0], + that.touchTranslation_[1], 0); + } else if (event.targetTouches.length == 2) { + // Exactly 2 fingers inside the canvas => Setup a pinch/zoom + that.touchZoom_ = that.GetTouchZoom(event); + var z0 = that.touchZoom_; + that.ViewportMouseDown(that.pimpl_, + 2 /* right button */, + z0[0], + z0[1], 0); + } + } + }); + } + + public ResetTouch() { + if (this.touchTranslation_ || + this.touchZoom_) { + this.ViewportMouseUp(this.pimpl_); + } + + this.touchTranslation_ = false; + this.touchZoom_ = false; + } + + public GetTouchTranslation(event) { + var touch = event.targetTouches[0]; + return [ + touch.pageX, + touch.pageY + ]; + } + + public GetTouchZoom(event) { + var touch1 = event.targetTouches[0]; + var touch2 = event.targetTouches[1]; + var dx = (touch1.pageX - touch2.pageX); + var dy = (touch1.pageY - touch2.pageY); + var d = Math.sqrt(dx * dx + dy * dy); + return [ + (touch1.pageX + touch2.pageX) / 2.0, + (touch1.pageY + touch2.pageY) / 2.0, + d + ]; + } + +} +} + \ No newline at end of file diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/WebAssembly/CMakeLists.txt --- a/Platforms/WebAssembly/CMakeLists.txt Sat Jul 14 11:20:07 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -# Usage (Linux): -# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake .. - -cmake_minimum_required(VERSION 2.8.3) - - -##################################################################### -## Configuration of the Emscripten compiler for WebAssembly target -##################################################################### - -set(WASM_FLAGS "-s WASM=1") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${CMAKE_SOURCE_DIR}/library.js") - -# Handling of memory -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") # Resize -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") # 512MB -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912") # 512MB + resize -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824") # 1GB + resize - -# To debug exceptions -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2") - - -##################################################################### -## Build a static library containing the Orthanc Stone framework -##################################################################### - -include(../../Resources/CMake/OrthancStoneParameters.cmake) - -SET(ORTHANC_SANDBOXED ON) -SET(ENABLE_SDL OFF) - -include(../../Resources/CMake/OrthancStoneConfiguration.cmake) - -add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES}) - - - - - - -# Regenerate a dummy "library.c" file each time the "library.js" file -# is modified, so as to force a new execution of the linking -add_custom_command( - OUTPUT "${AUTOGENERATED_DIR}/library.c" - COMMAND ${CMAKE_COMMAND} -E touch "${AUTOGENERATED_DIR}/library.c" "" - DEPENDS "${CMAKE_SOURCE_DIR}/library.js") diff -r 106a0f9781d9 -r c887eddd48f1 Platforms/WebAssembly/library.js --- a/Platforms/WebAssembly/library.js Sat Jul 14 11:20:07 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -mergeInto(LibraryManager.library, { -}); diff -r 106a0f9781d9 -r c887eddd48f1 README --- a/README Sat Jul 14 11:20:07 2018 +0200 +++ b/README Tue Jul 17 14:43:42 2018 +0200 @@ -72,6 +72,10 @@ * Optionally, SDL, a cross-platform multimedia library: https://www.libsdl.org/ +Prerequisites to compile on Ubuntu: +``` +sudo apt-get install -y libcairo-dev libpixman-1-dev libsdl2-dev +``` Installation and usage ---------------------- diff -r 106a0f9781d9 -r c887eddd48f1 Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Sat Jul 14 11:20:07 2018 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Tue Jul 17 14:43:42 2018 +0200 @@ -141,21 +141,38 @@ ## All the source files required to build Stone of Orthanc ##################################################################### +set(APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/IBasicApplication.h + ${ORTHANC_STONE_ROOT}/Applications/BasicApplicationContext.cpp + ) + if (NOT ORTHANC_SANDBOXED) set(PLATFORM_SOURCES + ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceCommandBase.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/Oracle.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleWebService.h ) - set(APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/BasicApplicationContext.cpp - ${ORTHANC_STONE_ROOT}/Applications/IBasicApplication.cpp + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Sdl/BasicSdlApplication.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/BasicSdlApplicationContext.cpp ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlEngine.cpp ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOrthancSurface.cpp ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlWindow.cpp ) +else() + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Wasm/StartupParametersBuilder.cpp + ) + + set(STONE_WASM_SOURCES + ${ORTHANC_STONE_ROOT}/Platforms/Wasm/Defaults.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmWebService.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmViewport.cpp + ) endif() list(APPEND ORTHANC_STONE_SOURCES @@ -163,11 +180,13 @@ #${ORTHANC_STONE_ROOT}/Framework/Layers/SiblingSliceLocationFactory.cpp #${ORTHANC_STONE_ROOT}/Framework/Layers/SingleFrameRendererFactory.cpp ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp + ${ORTHANC_STONE_ROOT}/Framework/SmartLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/CircleMeasureTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/ColorFrameRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/DicomStructureSetRendererFactory.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/FrameRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/GrayscaleFrameRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Layers/ILayerSource.h ${ORTHANC_STONE_ROOT}/Framework/Layers/LayerSourceBase.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/LineLayerRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/LineMeasureTracker.cpp @@ -195,6 +214,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoContext.cpp ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoFont.cpp ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoSurface.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/IStatusBar.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/WidgetViewport.cpp ${ORTHANC_STONE_ROOT}/Framework/Volumes/ImageBuffer3D.cpp ${ORTHANC_STONE_ROOT}/Framework/Volumes/SlicedVolumeBase.cpp @@ -210,6 +230,12 @@ ${ORTHANC_STONE_ROOT}/Framework/Widgets/WidgetBase.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/WorldSceneWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageType.h + ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp @@ -229,3 +255,4 @@ ${SDL_SOURCES} ${BOOST_EXTENDED_SOURCES} ) + diff -r 106a0f9781d9 -r c887eddd48f1 UnitTestsSources/TestMessageBroker.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/TestMessageBroker.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -0,0 +1,109 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "gtest/gtest.h" + +#include "../Framework/Messages/MessageBroker.h" +#include "../Framework/Messages/IMessage.h" +#include "../Framework/Messages/IObservable.h" +#include "../Framework/Messages/IObserver.h" +#include "../Framework/StoneEnumerations.h" + + +static int globalCounter = 0; +class MyObserver : public OrthancStone::IObserver +{ + +public: + MyObserver(OrthancStone::MessageBroker& broker) + : OrthancStone::IObserver(broker) + {} + + + void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) { + if (message.GetType() == OrthancStone::MessageType_Generic) { + globalCounter++; + } + + } + +}; + + +TEST(MessageBroker, NormalUsage) +{ + OrthancStone::MessageBroker broker; + OrthancStone::IObservable observable(broker); + + globalCounter = 0; + + OrthancStone::IMessage genericMessage(OrthancStone::MessageType_Generic); + + // no observers have been registered -> nothing shall happen + observable.EmitMessage(genericMessage); + + ASSERT_EQ(0, globalCounter); + + // register an observer, check it is called + MyObserver observer(broker); + observable.RegisterObserver(observer); + + observable.EmitMessage(genericMessage); + + ASSERT_EQ(1, globalCounter); + + // check the observer is not called when another message is issued + OrthancStone::IMessage wrongMessage(OrthancStone::MessageType_GeometryReady); + // no observers have been registered + observable.EmitMessage(wrongMessage); + + ASSERT_EQ(1, globalCounter); + + // unregister the observer, make sure nothing happens afterwards + observable.UnregisterObserver(observer); + observable.EmitMessage(genericMessage); + ASSERT_EQ(1, globalCounter); +} + +TEST(MessageBroker, DeleteObserverWhileRegistered) +{ + OrthancStone::MessageBroker broker; + OrthancStone::IObservable observable(broker); + + globalCounter = 0; + + OrthancStone::IMessage genericMessage(OrthancStone::MessageType_Generic); + + { + // register an observer, check it is called + MyObserver observer(broker); + observable.RegisterObserver(observer); + + observable.EmitMessage(genericMessage); + + ASSERT_EQ(1, globalCounter); + } + + // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !) + observable.EmitMessage(genericMessage); + + ASSERT_EQ(1, globalCounter); +} diff -r 106a0f9781d9 -r c887eddd48f1 UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Sat Jul 14 11:20:07 2018 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Tue Jul 17 14:43:42 2018 +0200 @@ -55,7 +55,7 @@ for (size_t i = 0; i < loader.GetSliceCount(); i++) { - const_cast(loader).ScheduleLoadSliceImage(i, SliceImageQuality_Full); + const_cast(loader).ScheduleLoadSliceImage(i, SliceImageQuality_FullPng); } }