# 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);
}
}