Mercurial > hg > orthanc-stone
changeset 561:1201b12eb9f8 dev
merge default -> dev
author | Alain Mazy <alain@mazy.be> |
---|---|
date | Thu, 18 Apr 2019 09:30:00 +0200 |
parents | d5579bdc59b5 (diff) aaeec7be8fb7 (current diff) |
children | 37e396ae08a3 9b5d1bae869d |
files | Applications/StoneApplicationContext.h Framework/Layers/GrayscaleFrameRenderer.cpp Framework/Radiography/RadiographyWidget.cpp Framework/StoneEnumerations.h Framework/Viewport/WidgetViewport.cpp Framework/Widgets/WorldSceneWidget.cpp Resources/CMake/OrthancStoneConfiguration.cmake UnitTestsSources/TestMessageBroker.cpp |
diffstat | 145 files changed, 5901 insertions(+), 1837 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Wed Apr 17 17:57:50 2019 +0200 +++ b/.hgignore Thu Apr 18 09:30:00 2019 +0200 @@ -1,7 +1,13 @@ CMakeLists.txt.user Platforms/Generic/ThirdPartyDownloads/ +Applications/build-* Applications/Qt/archive/ Applications/Samples/ThirdPartyDownloads/ +Applications/Samples/rt-viewer-demo/build-sdl-msvc15/ +Applications/Samples/rt-viewer-demo/build-tsc-output/ +Applications/Samples/rt-viewer-demo/build-wasm/ +Applications/Samples/rt-viewer-demo/build-web/ +Applications/Samples/rt-viewer-demo/ThirdPartyDownloads/ Applications/Samples/build-wasm/ Applications/Samples/build-web/ Applications/Samples/node_modules/ @@ -19,3 +25,4 @@ Resources/CodeGeneration/testWasmIntegrated/build-wasm/ Resources/CodeGeneration/testWasmIntegrated/build-tsc/ Resources/CodeGeneration/testWasmIntegrated/build-final/ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgtags Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,1 @@ +90f3a60576a9f08dcf783752a7f67ce0615a5371 rtviewer19
--- a/Applications/Commands/BaseCommandBuilder.cpp Wed Apr 17 17:57:50 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. - **/ - -#include "BaseCommandBuilder.h" -#include "Core/OrthancException.h" -#include <iostream> -#include "Framework/StoneException.h" - -namespace OrthancStone -{ - ICommand* BaseCommandBuilder::CreateFromJson(const Json::Value& commandJson) - { - if (!commandJson.isObject() || !commandJson["command"].isString()) - { - throw StoneException(ErrorCode_CommandJsonInvalidFormat); - } - - if (commandJson["commandType"].isString() && commandJson["commandType"].asString() == "generic-no-arg-command") - { - printf("creating a simple command\n"); - return new GenericNoArgCommand(commandJson["command"].asString().c_str()); - } - else if (commandJson["commandType"].isString() && commandJson["commandType"].asString() == "generic-one-string-arg-command") - { - printf("creating a simple command\n"); - return new GenericNoArgCommand(commandJson["command"].asString().c_str()); - } - - return NULL; - } - -}
--- a/Applications/Commands/BaseCommandBuilder.h Wed Apr 17 17:57:50 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. - **/ - -#pragma once - -#include <map> -#include <memory> - -#include "ICommand.h" -#include "../../Applications/Commands/ICommandBuilder.h" - -// TODO: must be reworked completely (check trello) - -namespace OrthancStone -{ - class BaseCommandBuilder : public ICommandBuilder - { - public: - virtual ICommand* CreateFromJson(const Json::Value& commandJson); - }; -}
--- a/Applications/Commands/ICommand.h Wed Apr 17 17:57:50 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <json/json.h> - -// TODO: must be reworked completely (check trello) - -namespace OrthancStone -{ - class ICommand // TODO noncopyable - { - protected: - std::string name_; - ICommand(const std::string& name) - : name_(name) - {} - public: - virtual ~ICommand() - {} - virtual void Execute() = 0; -// virtual void Configure(const Json::Value& arguments) = 0; - const std::string& GetName() const - { - return name_; - } - }; - - - template <typename TCommand> - class BaseCommand : public ICommand - { - protected: - BaseCommand(const std::string& name) - : ICommand(name) - {} - - public: - static ICommand* Create() { - return new TCommand(); - } - - virtual void Configure(const Json::Value& arguments) { - } - }; - - class NoopCommand : public BaseCommand<NoopCommand> - { - public: - NoopCommand() - : BaseCommand("noop") - {} - virtual void Execute() {} - }; - - class GenericNoArgCommand : public BaseCommand<GenericNoArgCommand> - { - public: - GenericNoArgCommand(const std::string& name) - : BaseCommand(name) - {} - virtual void Execute() {} // TODO currently not used but this is not nice at all ! - }; - - class GenericOneStringArgCommand : public BaseCommand<GenericOneStringArgCommand> - { - std::string argument_; - public: - GenericOneStringArgCommand(const std::string& name, const std::string& argument) - : BaseCommand(name) - {} - - const std::string& GetArgument() const {return argument_;} - virtual void Execute() {} // TODO currently not used but this is not nice at all ! - }; - -}
--- a/Applications/Commands/ICommandBuilder.h Wed Apr 17 17:57:50 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <boost/noncopyable.hpp> -#include <json/json.h> - -#include "ICommand.h" - -namespace OrthancStone -{ - - class ICommandBuilder : public boost::noncopyable - { - public: - virtual ICommand* CreateFromJson(const Json::Value& commandJson) = 0; - }; -}
--- a/Applications/Commands/ICommandExecutor.h Wed Apr 17 17:57:50 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <boost/noncopyable.hpp> - -namespace OrthancStone -{ - class ICommandExecutor : public boost::noncopyable - { - - }; -}
--- a/Applications/Generic/NativeStoneApplicationContext.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Generic/NativeStoneApplicationContext.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -51,7 +51,7 @@ stopped_(true), updateDelayInMs_(100) // By default, 100ms between each refresh of the content { - srand(time(NULL)); + srand(static_cast<unsigned int>(time(NULL))); }
--- a/Applications/Generic/NativeStoneApplicationRunner.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Generic/NativeStoneApplicationRunner.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -141,6 +141,13 @@ if (parameters.count("verbose")) { Orthanc::Logging::EnableInfoLevel(true); + LOG(INFO) << "Verbose logs are enabled"; + } + + if (parameters.count("trace")) + { + Orthanc::Logging::EnableTraceLevel(true); + VLOG(1) << "Trace logs are enabled"; } ParseCommandLineOptions(parameters);
--- a/Applications/IStoneApplication.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/IStoneApplication.h Thu Apr 18 09:30:00 2019 +0200 @@ -25,9 +25,6 @@ #include <boost/program_options.hpp> #include "../Framework/Viewport/WidgetViewport.h" #include "json/json.h" -#include "Commands/ICommand.h" -#include "Commands/BaseCommandBuilder.h" - namespace OrthancStone { @@ -52,6 +49,12 @@ virtual void Initialize(StoneApplicationContext* context, IStatusBar& statusBar, const boost::program_options::variables_map& parameters) = 0; + + /** + This method is meant to process messages received from the outside world (i.e. GUI) + */ + virtual void HandleSerializedMessage(const char* data) = 0; + #if ORTHANC_ENABLE_WASM==1 virtual void InitializeWasm() {} // specific initialization when the app is running in WebAssembly. This is called after the other Initialize() #endif @@ -61,14 +64,6 @@ virtual std::string GetTitle() const = 0; virtual IWidget* GetCentralWidget() = 0; - virtual void Finalize() = 0; - - virtual BaseCommandBuilder& GetCommandBuilder() = 0; - - virtual void ExecuteCommand(ICommand& command) - { - } }; - }
--- a/Applications/Qt/QCairoWidget.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Qt/QCairoWidget.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -125,7 +125,7 @@ { OrthancStone::NativeStoneApplicationContext::GlobalMutexLocker locker(*context_); - locker.GetCentralViewport().MouseDown(button, event->pos().x(), event->pos().y(), stoneModifiers); + locker.GetCentralViewport().MouseDown(button, event->pos().x(), event->pos().y(), stoneModifiers, std::vector<OrthancStone::Touch>()); } } @@ -140,7 +140,7 @@ void QCairoWidget::mouseMoveEvent(QMouseEvent* event) { OrthancStone::NativeStoneApplicationContext::GlobalMutexLocker locker(*context_); - locker.GetCentralViewport().MouseMove(event->pos().x(), event->pos().y()); + locker.GetCentralViewport().MouseMove(event->pos().x(), event->pos().y(), std::vector<OrthancStone::Touch>()); }
--- a/Applications/Samples/CMakeLists.txt Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/CMakeLists.txt Thu Apr 18 09:30:00 2019 +0200 @@ -1,53 +1,70 @@ -# Usage: see README file +# Usage (Linux): +# to build the WASM samples +# 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 .. -DENABLE_WASM=ON +# to build the Qt samples cmake_minimum_required(VERSION 2.8.3) - -# Automatically link Qt executables to qtmain target on Windows -# ("OLD" == do not link) -if(POLICY CMP0020) - cmake_policy(SET CMP0020 OLD) -endif() - -# Only interpret if() arguments as variables or keywords when unquoted. -# NEW = do NOT dereference *quoted* variables -if(POLICY CMP0054) - cmake_policy(SET CMP0054 NEW) -endif() - project(OrthancStone) include(../../Resources/CMake/OrthancStoneParameters.cmake) +if (OPENSSL_NO_CAPIENG) +add_definitions(-DOPENSSL_NO_CAPIENG=1) +endif() + + +# the following block has been borrowed from orthanc/**/Compiler.cmake +if (MSVC_MULTIPLE_PROCESSES) +# "If you omit the processMax argument in the /MP option, the +# compiler obtains the number of effective processors from the +# operating system, and then creates one process per effective +# processor" +# https://blog.kitware.com/cmake-building-with-all-your-cores/ +# https://docs.microsoft.com/en-us/cpp/build/reference/mp-build-with-multiple-processes +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") +endif() + + #set(ENABLE_DCMTK ON) set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") -# TODO: replace or compute STONE_SOURCES_DIR from CMAKE_CURRENT_LIST_FILE - if (ENABLE_WASM) ##################################################################### ## Configuration of the Emscripten compiler for WebAssembly target ##################################################################### - set(WASM_FLAGS "-s WASM=1 -O0 -g0") - message("*****************************************************************************") - message("WARNING: optimizations are disabled in emcc!!! Enable them for production use") - message("*****************************************************************************") + set(WASM_FLAGS "-s WASM=1") + set(WASM_FLAGS "${WASM_FLAGS} -s STRICT=1") # drops support for all deprecated build options + set(WASM_FLAGS "${WASM_FLAGS} -s FILESYSTEM=1") # if we don't include it, gen_uuid.c fails to build because srand, getpid(), ... are not defined + set(WASM_FLAGS "${WASM_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0") # actually enable exception catching + set(WASM_FLAGS "${WASM_FLAGS} -s ERROR_ON_MISSING_LIBRARIES=1") + + if (CMAKE_BUILD_TYPE MATCHES DEBUG) + set(WASM_FLAGS "${WASM_FLAGS} -g4") # generate debug information + set(WASM_FLAGS "${WASM_FLAGS} -s ASSERTIONS=2") # more runtime checks + else() + set(WASM_FLAGS "${WASM_FLAGS} -Os") # optimize for web (speed and size) + endif() + 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}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.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") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${WASM_FLAGS}") # not always clear which flags are for the compiler and which one are for the linker -> pass them all to the linker too + # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"'") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_STACK=128000000") add_definitions(-DORTHANC_ENABLE_WASM=1) set(ORTHANC_SANDBOXED ON) @@ -80,10 +97,12 @@ set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + ##################################################################### ## Build a static library containing the Orthanc Stone framework ##################################################################### + LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) include(../../Resources/CMake/OrthancStoneConfiguration.cmake) @@ -145,16 +164,6 @@ ) set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) target_link_libraries(${Target} OrthancStone) - - if (ENABLE_QT AND (CMAKE_SYSTEM_NAME STREQUAL "Windows")) - message("(ENABLE_QT and (CMAKE_SYSTEM_NAME matches \"Windows\")) is true") - add_custom_command( - TARGET ${Target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Core> $<TARGET_FILE_DIR:${Target}> - COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Widgets> $<TARGET_FILE_DIR:${Target}> - COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Gui> $<TARGET_FILE_DIR:${Target}> - ) - endif() endmacro() #BuildSingleFileSample(OrthancStoneEmpty EmptyApplication.h 1) @@ -171,12 +180,6 @@ if (ENABLE_QT OR ENABLE_WASM) - # GenerateCodeFromFlatBufferSchema("${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ApplicationCommands.fbs") - - list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES ${FLATC_AUTOGENERATED_SOURCES}) - message(STATUS "SIMPLE_VIEWER_APPLICATION_SOURCES = ${SIMPLE_VIEWER_APPLICATION_SOURCES}") - message(STATUS "FLATC_AUTOGENERATED_SOURCES = ${FLATC_AUTOGENERATED_SOURCES}") - if (ENABLE_QT) list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp @@ -196,16 +199,20 @@ list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.h ${STONE_WASM_SOURCES} ) endif() add_executable(OrthancStoneSimpleViewer - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.h ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Messages.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.h ${SIMPLE_VIEWER_APPLICATION_SOURCES} ) target_link_libraries(OrthancStoneSimpleViewer OrthancStone)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/CMakeLists.txt.old Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,248 @@ +# Usage: see README file + +cmake_minimum_required(VERSION 2.8.3) + +# Automatically link Qt executables to qtmain target on Windows +# ("OLD" == do not link) +if(POLICY CMP0020) + cmake_policy(SET CMP0020 OLD) +endif() + +# Only interpret if() arguments as variables or keywords when unquoted. +# NEW = do NOT dereference *quoted* variables +if(POLICY CMP0054) + cmake_policy(SET CMP0054 NEW) +endif() + +project(OrthancStone) + +include(../../Resources/CMake/OrthancStoneParameters.cmake) + +#set(ENABLE_DCMTK ON) + +set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") +set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") +set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") + +# TODO: replace or compute STONE_SOURCES_DIR from CMAKE_CURRENT_LIST_FILE + +if (ENABLE_WASM) + ##################################################################### + ## Configuration of the Emscripten compiler for WebAssembly target + ##################################################################### + + set(WASM_FLAGS "-s WASM=1 -O0 -g0") + message("*****************************************************************************") + message("WARNING: optimizations are disabled in emcc!!! Enable them for production use") + message("*****************************************************************************") + 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}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.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") + + add_definitions(-DORTHANC_ENABLE_WASM=1) + set(ORTHANC_SANDBOXED ON) + +elseif (ENABLE_QT OR ENABLE_SDL) + + set(ENABLE_NATIVE ON) + set(ORTHANC_SANDBOXED OFF) + set(ENABLE_CRYPTO_OPTIONS ON) + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_WEB_CLIENT ON) + +endif() + +##################################################################### +## Configuration for Orthanc +##################################################################### + +# include(../../Resources/CMake/Version.cmake) + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.4.1") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + +##################################################################### +## Build a static library containing the Orthanc Stone framework +##################################################################### + +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +##################################################################### +## Build all the sample applications +##################################################################### + +include_directories(${ORTHANC_STONE_ROOT}) + +# files common to all samples +list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h + ) + +if (ENABLE_QT) + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleQtApplicationRunner.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.cpp + ) + + ORTHANC_QT_WRAP_UI(SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.ui + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.ui + ) + + ORTHANC_QT_WRAP_CPP(SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Qt/QCairoWidget.h + ${ORTHANC_STONE_ROOT}/Applications/Qt/QStoneMainWindow.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.h + ) +endif() + +if (ENABLE_NATIVE) + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainNative.cpp + ) + +elseif (ENABLE_WASM) + + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainWasm.cpp + ${STONE_WASM_SOURCES} + ) +endif() + + +macro(BuildSingleFileSample Target Header Sample) + add_executable(${Target} + ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header} + ${SAMPLE_APPLICATIONS_SOURCES} + ) + set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) + target_link_libraries(${Target} OrthancStone) + + if (ENABLE_QT AND (CMAKE_SYSTEM_NAME STREQUAL "Windows")) + message("(ENABLE_QT and (CMAKE_SYSTEM_NAME matches \"Windows\")) is true") + add_custom_command( + TARGET ${Target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Core> $<TARGET_FILE_DIR:${Target}> + COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Widgets> $<TARGET_FILE_DIR:${Target}> + COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Gui> $<TARGET_FILE_DIR:${Target}> + ) + endif() +endmacro() + +#BuildSingleFileSample(OrthancStoneEmpty EmptyApplication.h 1) +#BuildSingleFileSample(OrthancStoneTestPattern TestPatternApplication.h 2) +BuildSingleFileSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) +#BuildSingleFileSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) +#BuildSingleFileSample(OrthancStoneBasicPetCtFusion 5) +#BuildSingleFileSample(OrthancStoneSynchronizedSeries 6) +#BuildSingleFileSample(OrthancStoneLayoutPetCtFusion 7) +BuildSingleFileSample(OrthancStoneSimpleViewerSingleFile SimpleViewerApplicationSingleFile.h 8) # we keep that one just as a sample before we convert another sample to this pattern +BuildSingleFileSample(OrthancStoneSingleFrameEditor SingleFrameEditorApplication.h 9) + +##### SimpleViewer sample (Qt and WASM only) ####### + +if (ENABLE_QT OR ENABLE_WASM) + + # GenerateCodeFromFlatBufferSchema("${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ApplicationCommands.fbs") + + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES ${FLATC_AUTOGENERATED_SOURCES}) + message(STATUS "SIMPLE_VIEWER_APPLICATION_SOURCES = ${SIMPLE_VIEWER_APPLICATION_SOURCES}") + message(STATUS "FLATC_AUTOGENERATED_SOURCES = ${FLATC_AUTOGENERATED_SOURCES}") + + if (ENABLE_QT) + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/mainQt.cpp + ) + + ORTHANC_QT_WRAP_UI(SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui + ) + + ORTHANC_QT_WRAP_CPP(SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.h + ) + +elseif (ENABLE_WASM) + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp + ${STONE_WASM_SOURCES} + ) + endif() + + add_executable(OrthancStoneSimpleViewer + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Messages.h + ${SIMPLE_VIEWER_APPLICATION_SOURCES} + ) + target_link_libraries(OrthancStoneSimpleViewer OrthancStone) + +endif() + +##################################################################### +## Build the unit tests +##################################################################### + +if (ENABLE_NATIVE) + add_executable(UnitTests + ${GOOGLE_TEST_SOURCES} + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestExceptions.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp + ) + + target_link_libraries(UnitTests OrthancStone) +endif() + +##################################################################### +## Generate the documentation if Doxygen is present +##################################################################### + +find_package(Doxygen) +if (DOXYGEN_FOUND) + configure_file( + ${ORTHANC_STONE_ROOT}/Resources/OrthancStone.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen + @ONLY) + + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen + COMMENT "Generating documentation with Doxygen" VERBATIM + ) +else() + message("Doxygen not found. The documentation will not be built.") +endif()
--- a/Applications/Samples/SampleApplicationBase.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SampleApplicationBase.h Thu Apr 18 09:30:00 2019 +0200 @@ -41,30 +41,35 @@ class SampleApplicationBase : public IStoneApplication { protected: - BaseCommandBuilder commandBuilder_; - WorldSceneWidget* mainWidget_; // ownership is transfered to the application context + // ownership is transferred to the application context + WorldSceneWidget* mainWidget_; public: virtual void Initialize(StoneApplicationContext* context, IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) + const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE { } - virtual std::string GetTitle() const + virtual std::string GetTitle() const ORTHANC_OVERRIDE { return "Stone of Orthanc - Sample"; } - virtual BaseCommandBuilder& GetCommandBuilder() {return commandBuilder_;} + /** + * In the basic samples, the commands are handled by the platform adapter and NOT + * by the application handler + */ + virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE {}; - virtual void Finalize() {} - virtual IWidget* GetCentralWidget() {return mainWidget_;} + + virtual void Finalize() ORTHANC_OVERRIDE {} + virtual IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainWidget_;} #if ORTHANC_ENABLE_WASM==1 // default implementations for a single canvas named "canvas" in the HTML and an emtpy WasmApplicationAdapter - virtual void InitializeWasm() + virtual void InitializeWasm() ORTHANC_OVERRIDE { AttachWidgetToWasmViewport("canvas", mainWidget_); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Samples-status.md Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,103 @@ +Executable versions +================ +Generic options +---------------------- +``` +("help", "Display this help and exit") +("verbose", "Be verbose in logs") +("orthanc", boost::program_options::value<std::string>() + ->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<bool>() + ->default_value(true), "Check HTTPS certificates") +``` +OrthancStoneSimpleViewer +------------------------------------- +- Options: + ``` + - "studyId", std::string, "Orthanc ID of the study" + ``` +- study loading works OK +- Invert does not work: +``` +void SimpleViewerApplication::ExecuteAction(SimpleViewerApplication::Actions action) + { + // TODO + } +``` + +OrthancStoneSimpleViewerSingleFile +------------------------------------- +- Options: + ``` + - "studyId", std::string, "Orthanc ID of the study" + ``` + +Study loading works. + +The `line` and `circle` buttons work and call this: +``` +virtual void OnTool1Clicked() +{ + currentTool_ = Tools_LineMeasure; +} + +virtual void OnTool2Clicked() +{ + currentTool_ = Tools_CircleMeasure; +} +``` +The `action1` and `action2` buttons are not connected + +The following is displayed in the console at launch time: +``` +W0313 12:20:12.790449 NativeStoneApplicationRunner.cpp:55] Use the key "s" to reinitialize the layout +W0313 12:20:12.790449 NativeStoneApplicationRunner.cpp:55] Use the key "n" to go to next image in the main viewport +``` +However, when looking at `MainWidgetInteractor::KeyPressed` (`SimpleViewerApplicationSingleFile.h:169`), only the following is processed: +- 's': reset layout +- 'l': select line tool +- 'c': select circle tool + +OrthancStoneSingleFrame +------------------------------------- +``` +generic.add_options() +("instance", boost::program_options::value<std::string>(), +"Orthanc ID of the instance") +("frame", boost::program_options::value<unsigned int>() + ->default_value(0), +"Number of the frame, for multi-frame DICOM instances") +("smooth", boost::program_options::value<bool>() + ->default_value(true), +"Enable bilinear interpolation to smooth the image"); +``` +only key handled in `KeyPressed` is `s` to call `widget.FitContent()` + + +OrthancStoneSingleFrameEditor +------------------------------------- +``` +generic.add_options() +("instance", boost::program_options::value<std::string>(), +"Orthanc ID of the instance") +("frame", boost::program_options::value<unsigned int>() + ->default_value(0), +"Number of the frame, for multi-frame DICOM instances"); +``` +Available commands in `KeyPressed` (`SingleFrameEditorApplication.h:280`): +- 'a' widget.FitContent() +- 'c' Crop tool +- 'm' Mask tool +- 'd' dump to json and diplay result (?) +- 'e' export current view to Dicom with dummy tags (?) +- 'i' wdiget.SwitchInvert +- 't' Move tool +- 'n' switch between nearest and bilinear interpolation +- 'r' Rotate tool +- 's' Resize tool +- 'w' Windowing tool +- 'ctrl+y' redo +- 'ctrl+z' undo
--- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -32,37 +32,37 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector<Touch>& displayTouches) { if (button == MouseButton_Left) { - if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_LineMeasure) + if (application_.GetCurrentTool() == Tool_LineMeasure) { return new LineMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, application_.GetFont()); } - else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_CircleMeasure) + else if (application_.GetCurrentTool() == Tool_CircleMeasure) { return new CircleMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, application_.GetFont()); } - else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_Crop) + else if (application_.GetCurrentTool() == Tool_Crop) { // TODO } - else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_Windowing) + else if (application_.GetCurrentTool() == Tool_Windowing) { // TODO } - else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_Zoom) + else if (application_.GetCurrentTool() == Tool_Zoom) { // TODO } - else if (application_.GetCurrentTool() == SimpleViewerApplication::Tools_Pan) + else if (application_.GetCurrentTool() == Tool_Pan) { // TODO } - } return NULL; }
--- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.h Thu Apr 18 09:30:00 2019 +0200 @@ -50,7 +50,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar); + IStatusBar* statusBar, + const std::vector<Touch>& displayTouches); virtual void MouseOver(CairoContext& context, WorldSceneWidget& widget,
--- a/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -27,8 +27,15 @@ #include <ui_SimpleViewerMainWindow.h> #include "../SimpleViewerApplication.h" + namespace SimpleViewer { + template<typename T, typename U> + bool ExecuteCommand(U* handler, const T& command) + { + std::string serializedCommand = StoneSerialize(command); + StoneDispatchToHandler(serializedCommand, handler); + } SimpleViewerMainWindow::SimpleViewerMainWindow( OrthancStone::NativeStoneApplicationContext& context, @@ -67,43 +74,36 @@ void SimpleViewerMainWindow::cropClicked() { - GenericNoArgCommand command("selectTool:crop"); - stoneApplication_.ExecuteCommand(command); + stoneApplication_.ExecuteCommand(SelectTool(Tool_Crop)); } void SimpleViewerMainWindow::undoCropClicked() { - GenericNoArgCommand command("action:undo-crop"); - stoneApplication_.ExecuteCommand(command); + stoneApplication_.ExecuteCommand(Action(ActionType_UndoCrop)); } void SimpleViewerMainWindow::lineClicked() { - GenericNoArgCommand command("selectTool:line-measure"); - stoneApplication_.ExecuteCommand(command); + stoneApplication_.ExecuteCommand(SelectTool(Tool_LineMeasure)); } void SimpleViewerMainWindow::circleClicked() { - GenericNoArgCommand command("selectTool:circle-measure"); - stoneApplication_.ExecuteCommand(command); + stoneApplication_.ExecuteCommand(SelectTool(Tool_CircleMeasure)); } void SimpleViewerMainWindow::windowingClicked() { - GenericNoArgCommand command("selectTool:windowing"); - stoneApplication_.ExecuteCommand(command); + stoneApplication_.ExecuteCommand(SelectTool(Tool_Windowing)); } void SimpleViewerMainWindow::rotateClicked() { - GenericNoArgCommand command("action:rotate"); - stoneApplication_.ExecuteCommand(command); + stoneApplication_.ExecuteCommand(Action(ActionType_Rotate)); } void SimpleViewerMainWindow::invertClicked() { - GenericNoArgCommand command("action:invert"); - stoneApplication_.ExecuteCommand(command); + stoneApplication_.ExecuteCommand(Action(ActionType_Invert)); } }
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -153,13 +153,16 @@ void SimpleViewerApplication::LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId) { LOG(INFO) << "Loading thumbnail for series " << seriesId; + SliceViewerWidget* thumbnailWidget = new SliceViewerWidget(IObserver::GetBroker(), "thumbnail-series-" + seriesId); thumbnails_.push_back(thumbnailWidget); thumbnailsLayout_->AddWidget(thumbnailWidget); + thumbnailWidget->RegisterObserverCallback( new Callable<SimpleViewerApplication, SliceViewerWidget::GeometryChangedMessage> (*this, &SimpleViewerApplication::OnWidgetGeometryChanged)); + smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0); thumbnailWidget->SetInteractor(*thumbnailInteractor_); } @@ -180,53 +183,29 @@ smartLoader_->SetFrameInWidget(*mainWidget_, 0, instancesIdsPerSeriesId_[seriesId][0], 0); } - - - void SimpleViewerApplication::ExecuteCommand(ICommand& command) + bool SimpleViewerApplication::Handle(const StoneSampleCommands::SelectTool& value) { - statusBar_->SetMessage("received command: " + std::string(command.GetName())); - if (command.GetName() == "selectTool:circle-measure") - { - SelectTool(Tools_CircleMeasure); - } - else if (command.GetName() == "selectTool:line-measure") - { - SelectTool(Tools_LineMeasure); - } - else if (command.GetName() == "selectTool:crop") - { - SelectTool(Tools_Crop); - } - else if (command.GetName() == "selectTool:windowing") - { - SelectTool(Tools_Windowing); - } - else if (command.GetName() == "action:rotate") - { - ExecuteAction(Actions_Rotate); - } - else if (command.GetName() == "action:undo-crop") - { - ExecuteAction(Actions_UndoCrop); - } - else if (command.GetName() == "action:invert") - { - ExecuteAction(Actions_Invert); - } - else - { - command.Execute(); - } + currentTool_ = value.tool; + return true; } - void SimpleViewerApplication::ExecuteAction(SimpleViewerApplication::Actions action) + bool SimpleViewerApplication::Handle(const StoneSampleCommands::Action& value) { - // TODO - } - - void SimpleViewerApplication::SelectTool(SimpleViewerApplication::Tools tool) - { - currentTool_ = tool; + switch (value.type) + { + case ActionType_Invert: + // TODO + break; + case ActionType_UndoCrop: + // TODO + break; + case ActionType_Rotate: + // TODO + break; + default: + throw std::runtime_error("Action type not supported"); + } + return true; } #if ORTHANC_ENABLE_QT==1
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.h Thu Apr 18 09:30:00 2019 +0200 @@ -21,6 +21,12 @@ #pragma once + /* + This header contains the command definitions for the sample applications + */ +#include "Applications/Samples/StoneSampleCommands_generated.hpp" +using namespace StoneSampleCommands; + #include "Applications/IStoneApplication.h" #include "Framework/Layers/CircleMeasureTracker.h" @@ -53,10 +59,11 @@ namespace SimpleViewer { - class SimpleViewerApplication : - public IStoneApplication, - public IObserver, - public IObservable + class SimpleViewerApplication + : public IStoneApplication + , public IObserver + , public IObservable + , public StoneSampleCommands::IHandler { public: @@ -71,23 +78,9 @@ } }; - enum Tools { - Tools_LineMeasure, - Tools_CircleMeasure, - Tools_Crop, - Tools_Windowing, - Tools_Zoom, - Tools_Pan - }; + private: + Tool currentTool_; - enum Actions { - Actions_Rotate, - Actions_Invert, - Actions_UndoCrop - }; - - private: - Tools currentTool_; std::auto_ptr<MainWidgetInteractor> mainWidgetInteractor_; std::auto_ptr<ThumbnailInteractor> thumbnailInteractor_; LayoutWidget* mainLayout_; @@ -96,8 +89,6 @@ std::vector<SliceViewerWidget*> thumbnails_; std::map<std::string, std::vector<std::string> > instancesIdsPerSeriesId_; std::map<std::string, Json::Value> seriesTags_; - BaseCommandBuilder commandBuilder_; - unsigned int currentInstanceIndex_; OrthancStone::WidgetViewport* wasmViewport1_; OrthancStone::WidgetViewport* wasmViewport2_; @@ -111,7 +102,7 @@ SimpleViewerApplication(MessageBroker& broker) : IObserver(broker), IObservable(broker), - currentTool_(Tools_LineMeasure), + currentTool_(StoneSampleCommands::Tool_LineMeasure), mainLayout_(NULL), currentInstanceIndex_(0), wasmViewport1_(NULL), @@ -120,13 +111,13 @@ font_.LoadFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); } - virtual void Finalize() {} - virtual IWidget* GetCentralWidget() {return mainLayout_;} + virtual void Finalize() ORTHANC_OVERRIDE {} + virtual IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainLayout_;} - virtual void DeclareStartupOptions(boost::program_options::options_description& options); + virtual void DeclareStartupOptions(boost::program_options::options_description& options) ORTHANC_OVERRIDE; virtual void Initialize(StoneApplicationContext* context, IStatusBar& statusBar, - const boost::program_options::variables_map& parameters); + const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE; void OnStudyListReceived(const OrthancApiClient::JsonResponseReadyMessage& message); @@ -142,9 +133,8 @@ void SelectSeriesInMainViewport(const std::string& seriesId); - void SelectTool(Tools tool); - - Tools GetCurrentTool() const + + Tool GetCurrentTool() const { return currentTool_; } @@ -154,15 +144,26 @@ return font_; } - void ExecuteAction(Actions action); + // ExecuteAction method was empty (its body was a single "TODO" comment) + virtual bool Handle(const SelectTool& value) ORTHANC_OVERRIDE; + virtual bool Handle(const Action& value) ORTHANC_OVERRIDE; - virtual std::string GetTitle() const {return "SimpleViewer";} - virtual void ExecuteCommand(ICommand& command); - virtual BaseCommandBuilder& GetCommandBuilder() {return commandBuilder_;} + template<typename T> + bool ExecuteCommand(const T& cmd) + { + std::string cmdStr = StoneSampleCommands::StoneSerialize(cmd); + return StoneSampleCommands::StoneDispatchToHandler(cmdStr, this); + } + virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE + { + StoneSampleCommands::StoneDispatchToHandler(data, this); + } + + virtual std::string GetTitle() const ORTHANC_OVERRIDE {return "SimpleViewer";} #if ORTHANC_ENABLE_WASM==1 - virtual void InitializeWasm(); + virtual void InitializeWasm() ORTHANC_OVERRIDE; #endif #if ORTHANC_ENABLE_QT==1
--- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -32,7 +32,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector<Touch>& displayTouches) { if (button == MouseButton_Left) {
--- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SimpleViewer/ThumbnailInteractor.h Thu Apr 18 09:30:00 2019 +0200 @@ -47,7 +47,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar); + IStatusBar* statusBar, + const std::vector<Touch>& displayTouches); virtual void MouseOver(CairoContext& context, WorldSceneWidget& widget,
--- a/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -45,7 +45,7 @@ writer->write(event, &outputStr); - NotifyStatusUpdateFromCppToWeb(outputStr.str()); + NotifyStatusUpdateFromCppToWebWithString(outputStr.str()); } } // namespace SimpleViewer \ No newline at end of file
--- a/Applications/Samples/SimpleViewer/Wasm/simple-viewer.ts Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SimpleViewer/Wasm/simple-viewer.ts Thu Apr 18 09:30:00 2019 +0200 @@ -9,7 +9,7 @@ args: { } }; - wasmApplicationRunner.SendMessageToStoneApplication(JSON.stringify(command)); + wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); } function PerformAction(actionName: string) { @@ -19,7 +19,7 @@ args: { } }; - wasmApplicationRunner.SendMessageToStoneApplication(JSON.stringify(command)); + wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); } class SimpleViewerUI { @@ -59,8 +59,8 @@ // this method is called "from the C++ code" when the StoneApplication is updated. // it can be used to update the UI of the application -function UpdateWebApplication(statusUpdateMessageString: string) { - console.log("updating web application: ", statusUpdateMessageString); +function UpdateWebApplicationWithString(statusUpdateMessageString: string) { + console.log("updating web application with string: ", statusUpdateMessageString); let statusUpdateMessage = JSON.parse(statusUpdateMessageString); if ("event" in statusUpdateMessage) { @@ -70,3 +70,12 @@ } } } + +function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) { + console.log("updating web application with serialized message: ", statusUpdateMessageString); + console.log("<not supported in the simple viewer!>"); +} + +// make it available to other js scripts in the application +(<any> window).UpdateWebApplicationWithString = UpdateWebApplicationWithString; +(<any> window).UpdateWebApplicationWithSerializedMessage = UpdateWebApplicationWithSerializedMessage;
--- a/Applications/Samples/SimpleViewerApplicationSingleFile.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h Thu Apr 18 09:30:00 2019 +0200 @@ -66,7 +66,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector<Touch>& displayTouches) { if (button == MouseButton_Left) { @@ -121,16 +122,17 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector<Touch>& displayTouches) { if (button == MouseButton_Left) { - if (application_.currentTool_ == Tools_LineMeasure) + if (application_.currentTool_ == Tool_LineMeasure) { return new LineMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, application_.GetFont()); } - else if (application_.currentTool_ == Tools_CircleMeasure) + else if (application_.currentTool_ == Tool_CircleMeasure) { return new CircleMeasureTracker(statusBar, dynamic_cast<SliceViewerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, application_.GetFont()); @@ -177,11 +179,11 @@ break; case 'l': - application_.currentTool_ = Tools_LineMeasure; + application_.currentTool_ = Tool_LineMeasure; break; case 'c': - application_.currentTool_ = Tools_CircleMeasure; + application_.currentTool_ = Tool_CircleMeasure; break; default: @@ -203,35 +205,40 @@ { } - virtual void HandleMessageFromWeb(std::string& output, const std::string& input) + virtual void HandleSerializedMessageFromWeb(std::string& output, const std::string& input) { if (input == "select-tool:line-measure") { - viewerApplication_.currentTool_ = Tools_LineMeasure; - NotifyStatusUpdateFromCppToWeb("currentTool=line-measure"); + viewerApplication_.currentTool_ = Tool_LineMeasure; + NotifyStatusUpdateFromCppToWebWithString("currentTool=line-measure"); } else if (input == "select-tool:circle-measure") { - viewerApplication_.currentTool_ = Tools_CircleMeasure; - NotifyStatusUpdateFromCppToWeb("currentTool=circle-measure"); + viewerApplication_.currentTool_ = Tool_CircleMeasure; + NotifyStatusUpdateFromCppToWebWithString("currentTool=circle-measure"); } output = "ok"; } - virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) + virtual void NotifySerializedMessageFromCppToWeb(const std::string& statusUpdateMessage) { - UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str()); + UpdateStoneApplicationStatusFromCppWithSerializedMessage(statusUpdateMessage.c_str()); + } + + virtual void NotifyStatusUpdateFromCppToWebWithString(const std::string& statusUpdateMessage) + { + UpdateStoneApplicationStatusFromCppWithString(statusUpdateMessage.c_str()); } }; #endif - enum Tools { - Tools_LineMeasure, - Tools_CircleMeasure + enum Tool { + Tool_LineMeasure, + Tool_CircleMeasure }; - Tools currentTool_; + Tool currentTool_; std::auto_ptr<MainWidgetInteractor> mainWidgetInteractor_; std::auto_ptr<ThumbnailInteractor> thumbnailInteractor_; LayoutWidget* mainLayout_; @@ -253,7 +260,7 @@ public: SimpleViewerApplication(MessageBroker& broker) : IObserver(broker), - currentTool_(Tools_LineMeasure), + currentTool_(Tool_LineMeasure), mainLayout_(NULL), currentInstanceIndex_(0), wasmViewport1_(NULL), @@ -426,8 +433,8 @@ virtual void OnPushButton1Clicked() {} virtual void OnPushButton2Clicked() {} - virtual void OnTool1Clicked() { currentTool_ = Tools_LineMeasure;} - virtual void OnTool2Clicked() { currentTool_ = Tools_CircleMeasure;} + virtual void OnTool1Clicked() { currentTool_ = Tool_LineMeasure;} + virtual void OnTool2Clicked() { currentTool_ = Tool_CircleMeasure;} virtual void GetButtonNames(std::string& pushButton1, std::string& pushButton2,
--- a/Applications/Samples/SingleFrameApplication.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SingleFrameApplication.h Thu Apr 18 09:30:00 2019 +0200 @@ -60,7 +60,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector<Touch>& displayTouches) { return NULL; } @@ -137,7 +138,7 @@ if (slice >= static_cast<int>(source_->GetSliceCount())) { - slice = source_->GetSliceCount() - 1; + slice = static_cast<int>(source_->GetSliceCount()) - 1; } if (slice != static_cast<int>(slice_)) @@ -159,7 +160,7 @@ if (source_ != NULL && index < source_->GetSliceCount()) { - slice_ = index; + slice_ = static_cast<unsigned int>(index); #if 1 GetMainWidget().SetSlice(source_->GetSlice(slice_).GetGeometry());
--- a/Applications/Samples/SingleFrameEditorApplication.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/SingleFrameEditorApplication.h Thu Apr 18 09:30:00 2019 +0200 @@ -24,6 +24,7 @@ #include "SampleApplicationBase.h" #include "../../Framework/Radiography/RadiographyLayerCropTracker.h" +#include "../../Framework/Radiography/RadiographyLayerMaskTracker.h" #include "../../Framework/Radiography/RadiographyLayerMoveTracker.h" #include "../../Framework/Radiography/RadiographyLayerResizeTracker.h" #include "../../Framework/Radiography/RadiographyLayerRotateTracker.h" @@ -33,6 +34,7 @@ #include "../../Framework/Radiography/RadiographyWindowingTracker.h" #include "../../Framework/Radiography/RadiographySceneWriter.h" #include "../../Framework/Radiography/RadiographySceneReader.h" +#include "../../Framework/Radiography/RadiographyMaskLayer.h" #include <Core/HttpClient.h> #include <Core/Images/FontRegistry.h> @@ -60,6 +62,7 @@ Tool_Rotate, Tool_Crop, Tool_Resize, + Tool_Mask, Tool_Windowing }; @@ -67,6 +70,7 @@ StoneApplicationContext* context_; UndoRedoStack undoRedoStack_; Tool tool_; + RadiographyMaskLayer* maskLayer_; static double GetHandleSize() @@ -79,7 +83,8 @@ RadiographyEditorInteractor(MessageBroker& broker) : IObserver(broker), context_(NULL), - tool_(Tool_Move) + tool_(Tool_Move), + maskLayer_(NULL) { } @@ -88,6 +93,10 @@ context_ = &context; } + void SetMaskLayer(RadiographyMaskLayer* maskLayer) + { + maskLayer_ = maskLayer; + } virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& worldWidget, const ViewportGeometry& view, MouseButton button, @@ -96,7 +105,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector<Touch>& displayTouches) { RadiographyWidget& widget = dynamic_cast<RadiographyWidget&>(worldWidget); @@ -126,22 +136,27 @@ return NULL; } else if (tool_ == Tool_Crop || - tool_ == Tool_Resize) + tool_ == Tool_Resize || + tool_ == Tool_Mask) { RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected); - Corner corner; - if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) + ControlPoint controlPoint; + if (accessor.GetLayer().LookupControlPoint(controlPoint, x, y, view.GetZoom(), GetHandleSize())) { switch (tool_) { case Tool_Crop: return new RadiographyLayerCropTracker - (undoRedoStack_, widget.GetScene(), view, selected, x, y, corner); + (undoRedoStack_, widget.GetScene(), view, selected, controlPoint); + + case Tool_Mask: + return new RadiographyLayerMaskTracker + (undoRedoStack_, widget.GetScene(), view, selected, controlPoint); case Tool_Resize: return new RadiographyLayerResizeTracker - (undoRedoStack_, widget.GetScene(), selected, x, y, corner, + (undoRedoStack_, widget.GetScene(), selected, controlPoint, (modifiers & KeyboardModifiers_Shift)); default: @@ -207,6 +222,7 @@ { return NULL; } + return NULL; } virtual void MouseOver(CairoContext& context, @@ -231,25 +247,24 @@ if (widget.LookupSelectedLayer(selected) && (tool_ == Tool_Crop || - tool_ == Tool_Resize)) + tool_ == Tool_Resize || + tool_ == Tool_Mask)) { RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected); - Corner corner; - if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) + ControlPoint controlPoint; + if (accessor.GetLayer().LookupControlPoint(controlPoint, x, y, view.GetZoom(), GetHandleSize())) { - accessor.GetLayer().GetCorner(x, y, corner); - double z = 1.0 / view.GetZoom(); context.SetSourceColor(255, 0, 0); cairo_t* cr = context.GetObject(); cairo_set_line_width(cr, 2.0 * z); - cairo_move_to(cr, x - GetHandleSize() * z, y - GetHandleSize() * z); - cairo_line_to(cr, x + GetHandleSize() * z, y - GetHandleSize() * z); - cairo_line_to(cr, x + GetHandleSize() * z, y + GetHandleSize() * z); - cairo_line_to(cr, x - GetHandleSize() * z, y + GetHandleSize() * z); - cairo_line_to(cr, x - GetHandleSize() * z, y - GetHandleSize() * z); + cairo_move_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y - GetHandleSize() * z); + cairo_line_to(cr, controlPoint.x + GetHandleSize() * z, controlPoint.y - GetHandleSize() * z); + cairo_line_to(cr, controlPoint.x + GetHandleSize() * z, controlPoint.y + GetHandleSize() * z); + cairo_line_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y + GetHandleSize() * z); + cairo_line_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y - GetHandleSize() * z); cairo_stroke(cr); } } @@ -280,6 +295,11 @@ tool_ = Tool_Crop; break; + case 'm': + tool_ = Tool_Mask; + widget.Select(1); + break; + case 'd': { // dump to json and reload @@ -338,7 +358,7 @@ widget.SwitchInvert(); break; - case 'm': + case 't': tool_ = Tool_Move; break; @@ -407,6 +427,7 @@ boost::shared_ptr<RadiographyScene> scene_; RadiographyEditorInteractor interactor_; Orthanc::FontRegistry fontRegistry_; + RadiographyMaskLayer* maskLayer_; public: SingleFrameEditorApplication(MessageBroker& broker) : @@ -447,10 +468,11 @@ statusBar.SetMessage("Use the key \"e\" to export DICOM to the Orthanc server"); statusBar.SetMessage("Use the key \"f\" to switch full screen"); statusBar.SetMessage("Use the key \"i\" to invert contrast"); - statusBar.SetMessage("Use the key \"m\" to move objects"); + statusBar.SetMessage("Use the key \"m\" to modify the mask"); statusBar.SetMessage("Use the key \"n\" to switch between nearest neighbor and bilinear interpolation"); statusBar.SetMessage("Use the key \"r\" to rotate objects"); statusBar.SetMessage("Use the key \"s\" to resize objects (not applicable to DICOM layers)"); + statusBar.SetMessage("Use the key \"t\" to move (translate) objects"); statusBar.SetMessage("Use the key \"w\" to change windowing"); statusBar.SetMessage("Use the key \"ctrl-z\" to undo action"); @@ -468,7 +490,10 @@ fontRegistry_.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); scene_.reset(new RadiographyScene(GetBroker())); - scene_->LoadDicomFrame(context->GetOrthancApiClient(), instance, 0, false, NULL); + + RadiographyLayer& dicomLayer = scene_->LoadDicomFrame(context->GetOrthancApiClient(), instance, 0, false, NULL); + //scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0); + // = scene_->LoadDicomFrame(context->GetOrthancApiClient(), "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false, NULL); #if !defined(ORTHANC_ENABLE_WASM) || ORTHANC_ENABLE_WASM != 1 Orthanc::HttpClient::ConfigureSsl(true, "/etc/ssl/certs/ca-certificates.crt"); @@ -476,6 +501,15 @@ //scene_->LoadDicomWebFrame(context->GetWebService()); + std::vector<Orthanc::ImageProcessing::ImagePoint> mask; + mask.push_back(Orthanc::ImageProcessing::ImagePoint(1100, 100)); + mask.push_back(Orthanc::ImageProcessing::ImagePoint(1100, 1000)); + mask.push_back(Orthanc::ImageProcessing::ImagePoint(2000, 1000)); + mask.push_back(Orthanc::ImageProcessing::ImagePoint(2200, 150)); + mask.push_back(Orthanc::ImageProcessing::ImagePoint(1500, 550)); + maskLayer_ = dynamic_cast<RadiographyMaskLayer*>(&(scene_->LoadMask(mask, dynamic_cast<RadiographyDicomLayer&>(dicomLayer), 128.0f, NULL))); + interactor_.SetMaskLayer(maskLayer_); + { RadiographyLayer& layer = scene_->LoadText(fontRegistry_.GetFont(0), "Hello\nworld", NULL); layer.SetResizeable(true);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/StoneSampleCommands.yml Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,35 @@ +# +# 1 2 3 4 5 6 7 8 +# 345678901234567890123456789012345678901234567890123456789012345678901234567890 +# +rootName: StoneSampleCommands + +# +---------------------------------+ +# | Messages from TypeScript to C++ | +# +---------------------------------+ + +enum Tool: + - LineMeasure + - CircleMeasure + - Crop + - Windowing + - Zoom + - Pan + - Move + - Rotate + - Resize + - Mask + +struct SelectTool: + __handler: cpp + tool: Tool + +enum ActionType: + - UndoCrop + - Rotate + - Invert + +struct Action: + __handler: cpp + type: ActionType +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/StoneSampleCommands_generate.py Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,16 @@ +import sys +import os + +# add the generation script location to the search paths +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'Resources', 'CodeGeneration')) + +# import the code generation tooling script +import stonegentool + +schemaFile = os.path.join(os.path.dirname(__file__), 'StoneSampleCommands.yml') +outDir = os.path.dirname(__file__) + +# ignition! +stonegentool.Process(schemaFile, outDir) + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/StoneSampleCommands_generated.hpp Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,703 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 + +Generated on 2019-03-18 12:07:42.696093 by stonegentool + +*/ +#pragma once + +#include <exception> +#include <iostream> +#include <string> +#include <sstream> +#include <assert.h> +#include <memory> +#include <json/json.h> + +//#define STONEGEN_NO_CPP11 1 + +#ifdef STONEGEN_NO_CPP11 +#define StoneSmartPtr std::auto_ptr +#else +#define StoneSmartPtr std::unique_ptr +#endif + +namespace StoneSampleCommands +{ + /** Throws in case of problem */ + inline void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asInt(); + } + + inline Json::Value _StoneSerializeValue(int32_t value) + { + Json::Value result(value); + return result; + } + + inline void _StoneDeserializeValue(Json::Value& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue; + } + + inline Json::Value _StoneSerializeValue(Json::Value value) + { + return value; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asDouble(); + } + + inline Json::Value _StoneSerializeValue(double value) + { + Json::Value result(value); + return result; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asBool(); + } + + inline Json::Value _StoneSerializeValue(bool value) + { + Json::Value result(value); + return result; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue( + std::string& destValue + , const Json::Value& jsonValue) + { + destValue = jsonValue.asString(); + } + + inline Json::Value _StoneSerializeValue(const std::string& value) + { + // the following is better than + Json::Value result(value.data(),value.data()+value.size()); + return result; + } + + inline std::string MakeIndent(size_t indent) + { + char* txt = reinterpret_cast<char*>(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!! + for(size_t i = 0; i < indent; ++i) + txt[i] = ' '; + txt[indent] = 0; + std::string retVal(txt); + free(txt); // NO EXCEPTION ABOVE !!!!!!!!!! + return retVal; + } + + // generic dumper + template<typename T> + std::ostream& StoneDumpValue(std::ostream& out, const T& value, size_t indent) + { + out << MakeIndent(indent) << value; + return out; + } + + // string dumper + inline std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, size_t indent) + { + out << MakeIndent(indent) << "\"" << value << "\""; + return out; + } + + /** Throws in case of problem */ + template<typename T> + void _StoneDeserializeValue( + std::map<std::string, T>& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + for ( + Json::Value::const_iterator itr = jsonValue.begin(); + itr != jsonValue.end(); + itr++) + { + std::string key; + _StoneDeserializeValue(key, itr.key()); + + T innerDestValue; + _StoneDeserializeValue(innerDestValue, *itr); + + destValue[key] = innerDestValue; + } + } + + template<typename T> + Json::Value _StoneSerializeValue(const std::map<std::string,T>& value) + { + Json::Value result(Json::objectValue); + + for (typename std::map<std::string, T>::const_iterator it = value.cbegin(); + it != value.cend(); ++it) + { + // it->first it->second + result[it->first] = _StoneSerializeValue(it->second); + } + return result; + } + + template<typename T> + std::ostream& StoneDumpValue(std::ostream& out, const std::map<std::string,T>& value, size_t indent) + { + out << MakeIndent(indent) << "{\n"; + for (typename std::map<std::string, T>::const_iterator it = value.cbegin(); + it != value.cend(); ++it) + { + out << MakeIndent(indent+2) << "\"" << it->first << "\" : "; + StoneDumpValue(out, it->second, indent+2); + } + out << MakeIndent(indent) << "}\n"; + return out; + } + + /** Throws in case of problem */ + template<typename T> + void _StoneDeserializeValue( + std::vector<T>& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + destValue.reserve(jsonValue.size()); + for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++) + { + T innerDestValue; + _StoneDeserializeValue(innerDestValue, jsonValue[i]); + destValue.push_back(innerDestValue); + } + } + + template<typename T> + Json::Value _StoneSerializeValue(const std::vector<T>& value) + { + Json::Value result(Json::arrayValue); + for (size_t i = 0; i < value.size(); ++i) + { + result.append(_StoneSerializeValue(value[i])); + } + return result; + } + + template<typename T> + std::ostream& StoneDumpValue(std::ostream& out, const std::vector<T>& value, size_t indent) + { + out << MakeIndent(indent) << "[\n"; + for (size_t i = 0; i < value.size(); ++i) + { + StoneDumpValue(out, value[i], indent+2); + } + out << MakeIndent(indent) << "]\n"; + return out; + } + + inline void StoneCheckSerializedValueTypeGeneric(const Json::Value& value) + { + if ((!value.isMember("type")) || (!value["type"].isString())) + { + std::stringstream ss; + ss << "Cannot deserialize value ('type' key invalid)"; + throw std::runtime_error(ss.str()); + } + } + + inline void StoneCheckSerializedValueType( + const Json::Value& value, std::string typeStr) + { + StoneCheckSerializedValueTypeGeneric(value); + + std::string actTypeStr = value["type"].asString(); + if (actTypeStr != typeStr) + { + std::stringstream ss; + ss << "Cannot deserialize type" << actTypeStr + << "into " << typeStr; + throw std::runtime_error(ss.str()); + } + } + + // end of generic methods + +// end of generic methods + + enum Tool { + Tool_LineMeasure, + Tool_CircleMeasure, + Tool_Crop, + Tool_Windowing, + Tool_Zoom, + Tool_Pan, + Tool_Move, + Tool_Rotate, + Tool_Resize, + Tool_Mask, + }; + + inline std::string ToString(const Tool& value) + { + if( value == Tool_LineMeasure) + { + return std::string("LineMeasure"); + } + if( value == Tool_CircleMeasure) + { + return std::string("CircleMeasure"); + } + if( value == Tool_Crop) + { + return std::string("Crop"); + } + if( value == Tool_Windowing) + { + return std::string("Windowing"); + } + if( value == Tool_Zoom) + { + return std::string("Zoom"); + } + if( value == Tool_Pan) + { + return std::string("Pan"); + } + if( value == Tool_Move) + { + return std::string("Move"); + } + if( value == Tool_Rotate) + { + return std::string("Rotate"); + } + if( value == Tool_Resize) + { + return std::string("Resize"); + } + if( value == Tool_Mask) + { + return std::string("Mask"); + } + std::stringstream ss; + ss << "Value \"" << value << "\" cannot be converted to Tool. Possible values are: " + << " LineMeasure = " << static_cast<int64_t>(Tool_LineMeasure) << ", " + << " CircleMeasure = " << static_cast<int64_t>(Tool_CircleMeasure) << ", " + << " Crop = " << static_cast<int64_t>(Tool_Crop) << ", " + << " Windowing = " << static_cast<int64_t>(Tool_Windowing) << ", " + << " Zoom = " << static_cast<int64_t>(Tool_Zoom) << ", " + << " Pan = " << static_cast<int64_t>(Tool_Pan) << ", " + << " Move = " << static_cast<int64_t>(Tool_Move) << ", " + << " Rotate = " << static_cast<int64_t>(Tool_Rotate) << ", " + << " Resize = " << static_cast<int64_t>(Tool_Resize) << ", " + << " Mask = " << static_cast<int64_t>(Tool_Mask) << ", " + << std::endl; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + inline void FromString(Tool& value, std::string strValue) + { + if( strValue == std::string("LineMeasure") ) + { + value = Tool_LineMeasure; + return; + } + if( strValue == std::string("CircleMeasure") ) + { + value = Tool_CircleMeasure; + return; + } + if( strValue == std::string("Crop") ) + { + value = Tool_Crop; + return; + } + if( strValue == std::string("Windowing") ) + { + value = Tool_Windowing; + return; + } + if( strValue == std::string("Zoom") ) + { + value = Tool_Zoom; + return; + } + if( strValue == std::string("Pan") ) + { + value = Tool_Pan; + return; + } + if( strValue == std::string("Move") ) + { + value = Tool_Move; + return; + } + if( strValue == std::string("Rotate") ) + { + value = Tool_Rotate; + return; + } + if( strValue == std::string("Resize") ) + { + value = Tool_Resize; + return; + } + if( strValue == std::string("Mask") ) + { + value = Tool_Mask; + return; + } + + std::stringstream ss; + ss << "String \"" << strValue << "\" cannot be converted to Tool. Possible values are: LineMeasure CircleMeasure Crop Windowing Zoom Pan Move Rotate Resize Mask "; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + + inline void _StoneDeserializeValue( + Tool& destValue, const Json::Value& jsonValue) + { + FromString(destValue, jsonValue.asString()); + } + + inline Json::Value _StoneSerializeValue(const Tool& value) + { + std::string strValue = ToString(value); + return Json::Value(strValue); + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const Tool& value, size_t indent = 0) + { + if( value == Tool_LineMeasure) + { + out << MakeIndent(indent) << "LineMeasure" << std::endl; + } + if( value == Tool_CircleMeasure) + { + out << MakeIndent(indent) << "CircleMeasure" << std::endl; + } + if( value == Tool_Crop) + { + out << MakeIndent(indent) << "Crop" << std::endl; + } + if( value == Tool_Windowing) + { + out << MakeIndent(indent) << "Windowing" << std::endl; + } + if( value == Tool_Zoom) + { + out << MakeIndent(indent) << "Zoom" << std::endl; + } + if( value == Tool_Pan) + { + out << MakeIndent(indent) << "Pan" << std::endl; + } + if( value == Tool_Move) + { + out << MakeIndent(indent) << "Move" << std::endl; + } + if( value == Tool_Rotate) + { + out << MakeIndent(indent) << "Rotate" << std::endl; + } + if( value == Tool_Resize) + { + out << MakeIndent(indent) << "Resize" << std::endl; + } + if( value == Tool_Mask) + { + out << MakeIndent(indent) << "Mask" << std::endl; + } + return out; + } + + + enum ActionType { + ActionType_UndoCrop, + ActionType_Rotate, + ActionType_Invert, + }; + + inline std::string ToString(const ActionType& value) + { + if( value == ActionType_UndoCrop) + { + return std::string("UndoCrop"); + } + if( value == ActionType_Rotate) + { + return std::string("Rotate"); + } + if( value == ActionType_Invert) + { + return std::string("Invert"); + } + std::stringstream ss; + ss << "Value \"" << value << "\" cannot be converted to ActionType. Possible values are: " + << " UndoCrop = " << static_cast<int64_t>(ActionType_UndoCrop) << ", " + << " Rotate = " << static_cast<int64_t>(ActionType_Rotate) << ", " + << " Invert = " << static_cast<int64_t>(ActionType_Invert) << ", " + << std::endl; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + inline void FromString(ActionType& value, std::string strValue) + { + if( strValue == std::string("UndoCrop") ) + { + value = ActionType_UndoCrop; + return; + } + if( strValue == std::string("Rotate") ) + { + value = ActionType_Rotate; + return; + } + if( strValue == std::string("Invert") ) + { + value = ActionType_Invert; + return; + } + + std::stringstream ss; + ss << "String \"" << strValue << "\" cannot be converted to ActionType. Possible values are: UndoCrop Rotate Invert "; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + + inline void _StoneDeserializeValue( + ActionType& destValue, const Json::Value& jsonValue) + { + FromString(destValue, jsonValue.asString()); + } + + inline Json::Value _StoneSerializeValue(const ActionType& value) + { + std::string strValue = ToString(value); + return Json::Value(strValue); + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const ActionType& value, size_t indent = 0) + { + if( value == ActionType_UndoCrop) + { + out << MakeIndent(indent) << "UndoCrop" << std::endl; + } + if( value == ActionType_Rotate) + { + out << MakeIndent(indent) << "Rotate" << std::endl; + } + if( value == ActionType_Invert) + { + out << MakeIndent(indent) << "Invert" << std::endl; + } + return out; + } + + + +#ifdef _MSC_VER +#pragma region SelectTool +#endif //_MSC_VER + + struct SelectTool + { + Tool tool; + + SelectTool(Tool tool = Tool()) + { + this->tool = tool; + } + }; + + inline void _StoneDeserializeValue(SelectTool& destValue, const Json::Value& value) + { + _StoneDeserializeValue(destValue.tool, value["tool"]); + } + + inline Json::Value _StoneSerializeValue(const SelectTool& value) + { + Json::Value result(Json::objectValue); + result["tool"] = _StoneSerializeValue(value.tool); + + return result; + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const SelectTool& value, size_t indent = 0) + { + out << MakeIndent(indent) << "{\n"; + out << MakeIndent(indent) << "tool:\n"; + StoneDumpValue(out, value.tool,indent+2); + out << "\n"; + + out << MakeIndent(indent) << "}\n"; + return out; + } + + inline void StoneDeserialize(SelectTool& destValue, const Json::Value& value) + { + StoneCheckSerializedValueType(value, "StoneSampleCommands.SelectTool"); + _StoneDeserializeValue(destValue, value["value"]); + } + + inline Json::Value StoneSerializeToJson(const SelectTool& value) + { + Json::Value result(Json::objectValue); + result["type"] = "StoneSampleCommands.SelectTool"; + result["value"] = _StoneSerializeValue(value); + return result; + } + + inline std::string StoneSerialize(const SelectTool& value) + { + Json::Value resultJson = StoneSerializeToJson(value); + std::string resultStr = resultJson.toStyledString(); + return resultStr; + } + +#ifdef _MSC_VER +#pragma endregion SelectTool +#endif //_MSC_VER + +#ifdef _MSC_VER +#pragma region Action +#endif //_MSC_VER + + struct Action + { + ActionType type; + + Action(ActionType type = ActionType()) + { + this->type = type; + } + }; + + inline void _StoneDeserializeValue(Action& destValue, const Json::Value& value) + { + _StoneDeserializeValue(destValue.type, value["type"]); + } + + inline Json::Value _StoneSerializeValue(const Action& value) + { + Json::Value result(Json::objectValue); + result["type"] = _StoneSerializeValue(value.type); + + return result; + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const Action& value, size_t indent = 0) + { + out << MakeIndent(indent) << "{\n"; + out << MakeIndent(indent) << "type:\n"; + StoneDumpValue(out, value.type,indent+2); + out << "\n"; + + out << MakeIndent(indent) << "}\n"; + return out; + } + + inline void StoneDeserialize(Action& destValue, const Json::Value& value) + { + StoneCheckSerializedValueType(value, "StoneSampleCommands.Action"); + _StoneDeserializeValue(destValue, value["value"]); + } + + inline Json::Value StoneSerializeToJson(const Action& value) + { + Json::Value result(Json::objectValue); + result["type"] = "StoneSampleCommands.Action"; + result["value"] = _StoneSerializeValue(value); + return result; + } + + inline std::string StoneSerialize(const Action& value) + { + Json::Value resultJson = StoneSerializeToJson(value); + std::string resultStr = resultJson.toStyledString(); + return resultStr; + } + +#ifdef _MSC_VER +#pragma endregion Action +#endif //_MSC_VER + +#ifdef _MSC_VER +#pragma region Dispatching code +#endif //_MSC_VER + + class IHandler + { + public: + virtual bool Handle(const SelectTool& value) = 0; + virtual bool Handle(const Action& value) = 0; + }; + + /** Service function for StoneDispatchToHandler */ + inline bool StoneDispatchJsonToHandler( + const Json::Value& jsonValue, IHandler* handler) + { + StoneCheckSerializedValueTypeGeneric(jsonValue); + std::string type = jsonValue["type"].asString(); + if (type == "") + { + // this should never ever happen + throw std::runtime_error("Caught empty type while dispatching"); + } + else if (type == "StoneSampleCommands.SelectTool") + { + SelectTool value; + _StoneDeserializeValue(value, jsonValue["value"]); + return handler->Handle(value); + } + else if (type == "StoneSampleCommands.Action") + { + Action value; + _StoneDeserializeValue(value, jsonValue["value"]); + return handler->Handle(value); + } + else + { + return false; + } + } + + /** Takes a serialized type and passes this to the handler */ + inline bool StoneDispatchToHandler(std::string strValue, IHandler* handler) + { + Json::Value readValue; + + Json::CharReaderBuilder builder; + Json::CharReader* reader = builder.newCharReader(); + + StoneSmartPtr<Json::CharReader> ptr(reader); + + std::string errors; + + bool ok = reader->parse( + strValue.c_str(), + strValue.c_str() + strValue.size(), + &readValue, + &errors + ); + if (!ok) + { + std::stringstream ss; + ss << "Jsoncpp parsing error: " << errors; + throw std::runtime_error(ss.str()); + } + return StoneDispatchJsonToHandler(readValue, handler); + } + +#ifdef _MSC_VER +#pragma endregion Dispatching code +#endif //_MSC_VER +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/StoneSampleCommands_generated.ts Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,333 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 + +Generated on 2019-03-18 12:07:42.696093 by stonegentool + +*/ + +function StoneCheckSerializedValueType(value: any, typeStr: string) +{ + StoneCheckSerializedValueTypeGeneric(value); + + if (value['type'] != typeStr) + { + throw new Error( + `Cannot deserialize type ${value['type']} into ${typeStr}`); + } +} + +function isString(val: any) :boolean +{ + return ((typeof val === 'string') || (val instanceof String)); +} + +function StoneCheckSerializedValueTypeGeneric(value: any) +{ + // console.//log("+-------------------------------------------------+"); + // console.//log("| StoneCheckSerializedValueTypeGeneric |"); + // console.//log("+-------------------------------------------------+"); + // console.//log("value = "); + // console.//log(value); + if ( (!('type' in value)) || (!isString(value.type)) ) + { + throw new Error( + "Cannot deserialize value ('type' key invalid)"); + } +} + +// end of generic methods + +export enum Tool { + LineMeasure = "LineMeasure", + CircleMeasure = "CircleMeasure", + Crop = "Crop", + Windowing = "Windowing", + Zoom = "Zoom", + Pan = "Pan", + Move = "Move", + Rotate = "Rotate", + Resize = "Resize", + Mask = "Mask" +}; + +export function Tool_FromString(strValue:string) : Tool +{ + if( strValue == "LineMeasure" ) + { + return Tool.LineMeasure; + } + if( strValue == "CircleMeasure" ) + { + return Tool.CircleMeasure; + } + if( strValue == "Crop" ) + { + return Tool.Crop; + } + if( strValue == "Windowing" ) + { + return Tool.Windowing; + } + if( strValue == "Zoom" ) + { + return Tool.Zoom; + } + if( strValue == "Pan" ) + { + return Tool.Pan; + } + if( strValue == "Move" ) + { + return Tool.Move; + } + if( strValue == "Rotate" ) + { + return Tool.Rotate; + } + if( strValue == "Resize" ) + { + return Tool.Resize; + } + if( strValue == "Mask" ) + { + return Tool.Mask; + } + + let msg : string = `String ${strValue} cannot be converted to Tool. Possible values are: LineMeasure, CircleMeasure, Crop, Windowing, Zoom, Pan, Move, Rotate, Resize, Mask`; + throw new Error(msg); +} + +export function Tool_ToString(value:Tool) : string +{ + if( value == Tool.LineMeasure ) + { + return "LineMeasure"; + } + if( value == Tool.CircleMeasure ) + { + return "CircleMeasure"; + } + if( value == Tool.Crop ) + { + return "Crop"; + } + if( value == Tool.Windowing ) + { + return "Windowing"; + } + if( value == Tool.Zoom ) + { + return "Zoom"; + } + if( value == Tool.Pan ) + { + return "Pan"; + } + if( value == Tool.Move ) + { + return "Move"; + } + if( value == Tool.Rotate ) + { + return "Rotate"; + } + if( value == Tool.Resize ) + { + return "Resize"; + } + if( value == Tool.Mask ) + { + return "Mask"; + } + + let msg : string = `Value ${value} cannot be converted to Tool. Possible values are: `; + { + let _LineMeasure_enumValue : string = Tool.LineMeasure; // enums are strings in stonecodegen, so this will work. + let msg_LineMeasure : string = `LineMeasure (${_LineMeasure_enumValue}), `; + msg = msg + msg_LineMeasure; + } + { + let _CircleMeasure_enumValue : string = Tool.CircleMeasure; // enums are strings in stonecodegen, so this will work. + let msg_CircleMeasure : string = `CircleMeasure (${_CircleMeasure_enumValue}), `; + msg = msg + msg_CircleMeasure; + } + { + let _Crop_enumValue : string = Tool.Crop; // enums are strings in stonecodegen, so this will work. + let msg_Crop : string = `Crop (${_Crop_enumValue}), `; + msg = msg + msg_Crop; + } + { + let _Windowing_enumValue : string = Tool.Windowing; // enums are strings in stonecodegen, so this will work. + let msg_Windowing : string = `Windowing (${_Windowing_enumValue}), `; + msg = msg + msg_Windowing; + } + { + let _Zoom_enumValue : string = Tool.Zoom; // enums are strings in stonecodegen, so this will work. + let msg_Zoom : string = `Zoom (${_Zoom_enumValue}), `; + msg = msg + msg_Zoom; + } + { + let _Pan_enumValue : string = Tool.Pan; // enums are strings in stonecodegen, so this will work. + let msg_Pan : string = `Pan (${_Pan_enumValue}), `; + msg = msg + msg_Pan; + } + { + let _Move_enumValue : string = Tool.Move; // enums are strings in stonecodegen, so this will work. + let msg_Move : string = `Move (${_Move_enumValue}), `; + msg = msg + msg_Move; + } + { + let _Rotate_enumValue : string = Tool.Rotate; // enums are strings in stonecodegen, so this will work. + let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `; + msg = msg + msg_Rotate; + } + { + let _Resize_enumValue : string = Tool.Resize; // enums are strings in stonecodegen, so this will work. + let msg_Resize : string = `Resize (${_Resize_enumValue}), `; + msg = msg + msg_Resize; + } + { + let _Mask_enumValue : string = Tool.Mask; // enums are strings in stonecodegen, so this will work. + let msg_Mask : string = `Mask (${_Mask_enumValue})`; + msg = msg + msg_Mask; + } + throw new Error(msg); +} + +export enum ActionType { + UndoCrop = "UndoCrop", + Rotate = "Rotate", + Invert = "Invert" +}; + +export function ActionType_FromString(strValue:string) : ActionType +{ + if( strValue == "UndoCrop" ) + { + return ActionType.UndoCrop; + } + if( strValue == "Rotate" ) + { + return ActionType.Rotate; + } + if( strValue == "Invert" ) + { + return ActionType.Invert; + } + + let msg : string = `String ${strValue} cannot be converted to ActionType. Possible values are: UndoCrop, Rotate, Invert`; + throw new Error(msg); +} + +export function ActionType_ToString(value:ActionType) : string +{ + if( value == ActionType.UndoCrop ) + { + return "UndoCrop"; + } + if( value == ActionType.Rotate ) + { + return "Rotate"; + } + if( value == ActionType.Invert ) + { + return "Invert"; + } + + let msg : string = `Value ${value} cannot be converted to ActionType. Possible values are: `; + { + let _UndoCrop_enumValue : string = ActionType.UndoCrop; // enums are strings in stonecodegen, so this will work. + let msg_UndoCrop : string = `UndoCrop (${_UndoCrop_enumValue}), `; + msg = msg + msg_UndoCrop; + } + { + let _Rotate_enumValue : string = ActionType.Rotate; // enums are strings in stonecodegen, so this will work. + let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `; + msg = msg + msg_Rotate; + } + { + let _Invert_enumValue : string = ActionType.Invert; // enums are strings in stonecodegen, so this will work. + let msg_Invert : string = `Invert (${_Invert_enumValue})`; + msg = msg + msg_Invert; + } + throw new Error(msg); +} + + + +export class SelectTool { + tool:Tool; + + constructor() { + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'StoneSampleCommands.SelectTool'; + container['value'] = this; + return JSON.stringify(container); + } + + public static StoneDeserialize(valueStr: string) : SelectTool + { + let value: any = JSON.parse(valueStr); + StoneCheckSerializedValueType(value, 'StoneSampleCommands.SelectTool'); + let result: SelectTool = value['value'] as SelectTool; + return result; + } +} +export class Action { + type:ActionType; + + constructor() { + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'StoneSampleCommands.Action'; + container['value'] = this; + return JSON.stringify(container); + } + + public static StoneDeserialize(valueStr: string) : Action + { + let value: any = JSON.parse(valueStr); + StoneCheckSerializedValueType(value, 'StoneSampleCommands.Action'); + let result: Action = value['value'] as Action; + return result; + } +} + +export interface IHandler { +}; + +/** Service function for StoneDispatchToHandler */ +export function StoneDispatchJsonToHandler( + jsonValue: any, handler: IHandler): boolean +{ + StoneCheckSerializedValueTypeGeneric(jsonValue); + let type: string = jsonValue["type"]; + if (type == "") + { + // this should never ever happen + throw new Error("Caught empty type while dispatching"); + } + else + { + return false; + } +} + +/** Takes a serialized type and passes this to the handler */ +export function StoneDispatchToHandler( + strValue: string, handler: IHandler): boolean +{ + // console.//log("+------------------------------------------------+"); + // console.//log("| StoneDispatchToHandler |"); + // console.//log("+------------------------------------------------+"); + // console.//log("strValue = "); + // console.//log(strValue); + let jsonValue: any = JSON.parse(strValue) + return StoneDispatchJsonToHandler(jsonValue, handler); +} \ No newline at end of file
--- a/Applications/Samples/Web/simple-viewer-single-file.ts Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/Web/simple-viewer-single-file.ts Thu Apr 18 09:30:00 2019 +0200 @@ -9,7 +9,7 @@ toolName: toolName } }; - wasmApplicationRunner.SendMessageToStoneApplication(JSON.stringify(command)); + wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); } @@ -19,7 +19,7 @@ commandType: "simple", args: {} }; - wasmApplicationRunner.SendMessageToStoneApplication(JSON.stringify(command)); + wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); } //initializes the buttons @@ -42,10 +42,20 @@ // this method is called "from the C++ code" when the StoneApplication is updated. // it can be used to update the UI of the application -function UpdateWebApplication(statusUpdateMessage: string) { +function UpdateWebApplicationWithString(statusUpdateMessage: string) { console.log(statusUpdateMessage); if (statusUpdateMessage.startsWith("series-description=")) { document.getElementById("series-description").innerText = statusUpdateMessage.split("=")[1]; } } + +function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) { + console.log("updating web application with serialized message: ", statusUpdateMessageString); + console.log("<not supported in the simple viewer (single file)!>"); +} + +// make it available to other js scripts in the application +(<any> window).UpdateWebApplicationWithString = UpdateWebApplicationWithString; + +(<any> window).UpdateWebApplicationWithSerializedMessage = UpdateWebApplicationWithSerializedMessage;
--- a/Applications/Samples/build-wasm.sh Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/build-wasm.sh Thu Apr 18 09:30:00 2019 +0200 @@ -1,15 +1,16 @@ #!/bin/bash # # usage: -# to build all targets: +# to build all targets in Debug: # ./build-wasm.sh # -# to build a single target: -# ./build-wasm.sh OrthancStoneSingleFrameEditor +# to build a single target in release: +# ./build-wasm.sh OrthancStoneSingleFrameEditor Release set -e target=${1:-all} +buildType=${2:-Debug} currentDir=$(pwd) samplesRootDir=$(pwd) @@ -18,16 +19,9 @@ cd $samplesRootDir/build-wasm source ~/apps/emsdk/emsdk_env.sh -cmake -G Ninja -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 .. \ - -DENABLE_WASM=ON - +cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=$buildType -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc -DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON ninja $target echo "-- building the web application -- " cd $currentDir -./build-web.sh +./build-web.sh \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/build-wasm.sh.old Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,33 @@ +#!/bin/bash +# +# usage: +# to build all targets: +# ./build-wasm.sh +# +# to build a single target: +# ./build-wasm.sh OrthancStoneSingleFrameEditor + +set -e + +target=${1:-all} + +currentDir=$(pwd) +samplesRootDir=$(pwd) + +mkdir -p $samplesRootDir/build-wasm +cd $samplesRootDir/build-wasm + +source ~/apps/emsdk/emsdk_env.sh +cmake -G Ninja -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 .. \ + -DENABLE_WASM=ON + +ninja $target + +echo "-- building the web application -- " +cd $currentDir +./build-web.sh
--- a/Applications/Samples/nginx.local.conf Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Samples/nginx.local.conf Thu Apr 18 09:30:00 2019 +0200 @@ -30,7 +30,7 @@ # reverse proxy orthanc location /orthanc/ { rewrite /orthanc(.*) $1 break; - proxy_pass http://127.0.0.1:8044; + proxy_pass http://127.0.0.1:8042; proxy_set_header Host $http_host; proxy_set_header my-auth-header good-token; proxy_request_buffering off;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/CMakeLists.txt Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,137 @@ +cmake_minimum_required(VERSION 2.8.3) +project(RtViewerDemo) + +if(MSVC) + add_definitions(/MP) + if (CMAKE_BUILD_TYPE MATCHES DEBUG) + add_definitions(/JMC) + endif() +endif() + +message("-------------------------------------------------------------------------------------------------------------------") +message("ORTHANC_FRAMEWORK_ROOT is set to ${ORTHANC_FRAMEWORK_ROOT}") +message("-------------------------------------------------------------------------------------------------------------------") + +if(NOT DEFINED ORTHANC_FRAMEWORK_ROOT) + message(FATAL_ERROR "The location of the Orthanc source repository must be set in the ORTHANC_FRAMEWORK_ROOT CMake variable") +endif() + +message("-------------------------------------------------------------------------------------------------------------------") +message("STONE_SOURCES_DIR is set to ${STONE_SOURCES_DIR}") +message("-------------------------------------------------------------------------------------------------------------------") + +if(NOT DEFINED STONE_SOURCES_DIR) + message(FATAL_ERROR "The location of the Stone of Orthanc source repository must be set in the STONE_SOURCES_DIR CMake variable") +endif() + +include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneParameters.cmake) + +if (OPENSSL_NO_CAPIENG) +add_definitions(-DOPENSSL_NO_CAPIENG=1) +endif() + +set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") +set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") +set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") + +if (ENABLE_WASM) + ##################################################################### + ## Configuration of the Emscripten compiler for WebAssembly target + ##################################################################### + + set(WASM_FLAGS "-s WASM=1") + set(WASM_FLAGS "${WASM_FLAGS} -s STRICT=1") # drops support for all deprecated build options + set(WASM_FLAGS "${WASM_FLAGS} -s FILESYSTEM=1") # if we don't include it, gen_uuid.c fails to build because srand, getpid(), ... are not defined + set(WASM_FLAGS "${WASM_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0") # actually enable exception catching + set(WASM_FLAGS "${WASM_FLAGS} -s ERROR_ON_MISSING_LIBRARIES=1") + + if (CMAKE_BUILD_TYPE MATCHES DEBUG) + set(WASM_FLAGS "${WASM_FLAGS} -g4") # generate debug information + set(WASM_FLAGS "${WASM_FLAGS} -s ASSERTIONS=2") # more runtime checks + else() + set(WASM_FLAGS "${WASM_FLAGS} -Os") # optimize for web (speed and size) + endif() + + 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} ${WASM_FLAGS}") # not always clear which flags are for the compiler and which one are for the linker -> pass them all to the linker too + # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"'") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_STACK=128000000") + + add_definitions(-DORTHANC_ENABLE_WASM=1) + set(ORTHANC_SANDBOXED ON) + +elseif (ENABLE_QT OR ENABLE_SDL) + + set(ENABLE_NATIVE ON) + set(ORTHANC_SANDBOXED OFF) + set(ENABLE_CRYPTO_OPTIONS ON) + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_WEB_CLIENT ON) + +endif() + +##################################################################### +## Configuration for Orthanc +##################################################################### + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.4.1") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + + +##################################################################### +## Build a static library containing the Orthanc Stone framework +##################################################################### + +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneConfiguration.cmake) + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +##################################################################### +## Build all the sample applications +##################################################################### + +include_directories(${ORTHANC_STONE_ROOT}) + +list(APPEND RTVIEWERDEMO_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h + ) + +if (ENABLE_WASM) + list(APPEND RTVIEWERDEMO_APPLICATION_SOURCES + ${STONE_WASM_SOURCES} + ) +endif() + +add_executable(RtViewerDemo + main.cpp + ${RTVIEWERDEMO_APPLICATION_SOURCES} +) +set_target_properties(RtViewerDemo PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=3) +target_include_directories(RtViewerDemo PRIVATE ${ORTHANC_STONE_ROOT}) +target_link_libraries(RtViewerDemo OrthancStone) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/build-sdl-msvc15.ps1 Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,24 @@ +if (-not (Test-Path "build-sdl-msvc15")) { + mkdir -p "build-sdl-msvc15" +} + +cd build-sdl-msvc15 + +cmake -G "Visual Studio 15 2017 Win64" -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DSTONE_SOURCES_DIR="$($pwd)\..\..\..\.." -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\..\..\..\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON .. + +if (!$?) { + Write-Error 'cmake configuration failed' -ErrorAction Stop +} + +cmake --build . --target RtViewerDemo --config Debug + +if (!$?) { + Write-Error 'cmake build failed' -ErrorAction Stop +} + +cd Debug + +.\RtViewerDemo.exe --ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/build-wasm.sh Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,29 @@ +#!/bin/bash +# +# usage: +# build-wasm BUILD_TYPE +# where BUILD_TYPE is Debug, RelWithDebInfo or Release + +set -e + +buildType=${1:-Debug} + +currentDir=$(pwd) +currentDirAbs=$(realpath $currentDir) + +mkdir -p build-wasm +cd build-wasm + +source ~/apps/emsdk/emsdk_env.sh +cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake \ +-DCMAKE_BUILD_TYPE=$buildType -DSTONE_SOURCES_DIR=$currentDirAbs/../../../../orthanc-stone \ +-DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDirAbs/../../../../orthanc \ +-DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON + +ninja $target + +echo "-- building the web application -- " +cd $currentDir +./build-web.sh + +echo "Launch start-serving-files.sh to access the web sample application locally"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/build-web.sh Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +target=${1:-all} +# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/ + +currentDir=$(pwd) +samplesRootDir=$(pwd) + +tscOutput=$samplesRootDir/build-tsc-output/ +outputDir=$samplesRootDir/build-web/ +mkdir -p "$outputDir" + +# files used by all single files samples +cp "$samplesRootDir/index.html" "$outputDir" +cp "$samplesRootDir/samples-styles.css" "$outputDir" + +# build rt-viewer-demo +cp $samplesRootDir/rt-viewer-demo.html $outputDir +tsc --project $samplesRootDir/rt-viewer-demo.tsconfig.json --outDir "$tscOutput" +browserify \ + "$tscOutput/orthanc-stone/Platforms/Wasm/logger.js" \ + "$tscOutput/orthanc-stone/Platforms/Wasm/stone-framework-loader.js" \ + "$tscOutput/orthanc-stone/Platforms/Wasm/wasm-application-runner.js" \ + "$tscOutput/orthanc-stone/Platforms/Wasm/wasm-viewport.js" \ + "$tscOutput/rt-viewer-sample/rt-viewer-demo.js" \ + -o "$outputDir/app-rt-viewer-demo.js" +cp "$currentDir/build-wasm/RtViewerDemo.js" $outputDir +cp "$currentDir/build-wasm/RtViewerDemo.wasm" $outputDir + +cd $currentDir
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/index.html Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,20 @@ +<!doctype html> + +<html lang="us"> + <head> + <meta charset="utf-8" /> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + + <!-- Disable pinch zoom on mobile devices --> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> + <meta name="HandheldFriendly" content="true" /> + + <title>Wasm Samples</title> + +<body> + <ul> + <li><a href="rt-viewer-demo.html?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9">RTSTRUCT + CT + RTDOSE viewer demo. Pplease replace the url arguments with suitable IDs (you can find those in the Orthanc Explorer, for instance)</a></li> + </ul> +</body> + +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/main.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,853 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. + **/ + +#include "Applications/IStoneApplication.h" +#include "Framework/Widgets/WorldSceneWidget.h" +#include "Framework/Widgets/LayoutWidget.h" + +#if ORTHANC_ENABLE_WASM==1 + #include "Platforms/Wasm/WasmPlatformApplicationAdapter.h" + #include "Platforms/Wasm/Defaults.h" + #include "Platforms/Wasm/WasmViewport.h" +#endif + +#if ORTHANC_ENABLE_QT==1 + #include "Qt/SampleMainWindow.h" + #include "Qt/SampleMainWindowWithButtons.h" +#endif + +#include "Framework/Layers/DicomSeriesVolumeSlicer.h" +#include "Framework/Widgets/SliceViewerWidget.h" +#include "Framework/Volumes/StructureSetLoader.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> +#include <Core/Images/ImageTraits.h> + +#include <boost/math/constants/constants.hpp> +#include "Framework/dev.h" +#include "Framework/Widgets/LayoutWidget.h" +#include "Framework/Layers/DicomStructureSetSlicer.h" + +namespace OrthancStone +{ + namespace Samples + { + class RtViewerDemoBaseApplication : public IStoneApplication + { + protected: + // ownership is transferred to the application context +#ifndef RESTORE_NON_RTVIEWERDEMO_BEHAVIOR + LayoutWidget* mainWidget_; +#else + WorldSceneWidget* mainWidget_; +#endif + + public: + virtual void Initialize(StoneApplicationContext* context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE + { + } + + virtual std::string GetTitle() const ORTHANC_OVERRIDE + { + return "Stone of Orthanc - Sample"; + } + + /** + * In the basic samples, the commands are handled by the platform adapter and NOT + * by the application handler + */ + virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE {}; + + + virtual void Finalize() ORTHANC_OVERRIDE {} + virtual IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainWidget_;} + +#if ORTHANC_ENABLE_WASM==1 + // default implementations for a single canvas named "canvas" in the HTML and an empty WasmApplicationAdapter + + virtual void InitializeWasm() ORTHANC_OVERRIDE + { + AttachWidgetToWasmViewport("canvas", mainWidget_); + } + + virtual WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(MessageBroker& broker) + { + return new WasmPlatformApplicationAdapter(broker, *this); + } +#endif + + }; + + // this application actually works in Qt and WASM + class RtViewerDemoBaseSingleCanvasWithButtonsApplication : public RtViewerDemoBaseApplication + { +public: + virtual void OnPushButton1Clicked() {} + virtual void OnPushButton2Clicked() {} + virtual void OnTool1Clicked() {} + virtual void OnTool2Clicked() {} + + virtual void GetButtonNames(std::string& pushButton1, + std::string& pushButton2, + std::string& tool1, + std::string& tool2 + ) { + pushButton1 = "action1"; + pushButton2 = "action2"; + tool1 = "tool1"; + tool2 = "tool2"; + } + +#if ORTHANC_ENABLE_QT==1 + virtual QStoneMainWindow* CreateQtMainWindow() { + return new SampleMainWindowWithButtons(dynamic_cast<OrthancStone::NativeStoneApplicationContext&>(*context_), *this); + } +#endif + + }; + + // this application actually works in SDL and WASM + class RtViewerDemoBaseApplicationSingleCanvas : public RtViewerDemoBaseApplication + { +public: + +#if ORTHANC_ENABLE_QT==1 + virtual QStoneMainWindow* CreateQtMainWindow() { + return new SampleMainWindow(dynamic_cast<OrthancStone::NativeStoneApplicationContext&>(*context_), *this); + } +#endif + }; + } +} + + + +namespace OrthancStone +{ + namespace Samples + { + template <Orthanc::PixelFormat T> + void ReadDistributionInternal(std::vector<float>& distribution, + const Orthanc::ImageAccessor& image) + { + const unsigned int width = image.GetWidth(); + const unsigned int height = image.GetHeight(); + + distribution.resize(width * height); + size_t pos = 0; + + for (unsigned int y = 0; y < height; y++) + { + for (unsigned int x = 0; x < width; x++, pos++) + { + distribution[pos] = Orthanc::ImageTraits<T>::GetFloatPixel(image, x, y); + } + } + } + + void ReadDistribution(std::vector<float>& distribution, + const Orthanc::ImageAccessor& image) + { + switch (image.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + ReadDistributionInternal<Orthanc::PixelFormat_Grayscale8>(distribution, image); + break; + + case Orthanc::PixelFormat_Grayscale16: + ReadDistributionInternal<Orthanc::PixelFormat_Grayscale16>(distribution, image); + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + ReadDistributionInternal<Orthanc::PixelFormat_SignedGrayscale16>(distribution, image); + break; + + case Orthanc::PixelFormat_Grayscale32: + ReadDistributionInternal<Orthanc::PixelFormat_Grayscale32>(distribution, image); + break; + + case Orthanc::PixelFormat_Grayscale64: + ReadDistributionInternal<Orthanc::PixelFormat_Grayscale64>(distribution, image); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + class DoseInteractor : public VolumeImageInteractor + { + private: + SliceViewerWidget& widget_; + size_t layer_; + DicomFrameConverter converter_; + + + + protected: + virtual void NotifySliceChange(const ISlicedVolume& slicedVolume, + const size_t& sliceIndex, + const Slice& slice) + { + converter_ = slice.GetConverter(); + + #if 0 + const OrthancVolumeImage& volume = dynamic_cast<const OrthancVolumeImage&>(slicedVolume); + + RenderStyle s = widget_.GetLayerStyle(layer_); + + if (volume.FitWindowingToRange(s, slice.GetConverter())) + { + printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); + widget_.SetLayerStyle(layer_, s); + } + #endif + } + + virtual void NotifyVolumeReady(const ISlicedVolume& slicedVolume) + { + const float percentile = 0.01f; + const OrthancVolumeImage& volume = dynamic_cast<const OrthancVolumeImage&>(slicedVolume); + + std::vector<float> distribution; + ReadDistribution(distribution, volume.GetImage().GetInternalImage()); + std::sort(distribution.begin(), distribution.end()); + + int start = static_cast<int>(std::ceil(distribution.size() * percentile)); + int end = static_cast<int>(std::floor(distribution.size() * (1.0f - percentile))); + + float a = 0; + float b = 0; + + if (start < end && + start >= 0 && + end < static_cast<int>(distribution.size())) + { + a = distribution[start]; + b = distribution[end]; + } + else if (!distribution.empty()) + { + // Too small distribution: Use full range + a = distribution.front(); + b = distribution.back(); + } + + //printf("%f %f\n", a, b); + + RenderStyle s = widget_.GetLayerStyle(layer_); + s.windowing_ = ImageWindowing_Custom; + s.customWindowCenter_ = static_cast<float>(converter_.Apply((a + b) / 2.0f)); + s.customWindowWidth_ = static_cast<float>(converter_.Apply(b - a)); + + // 96.210556 => 192.421112 + widget_.SetLayerStyle(layer_, s); + printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); + } + + public: + DoseInteractor(MessageBroker& broker, OrthancVolumeImage& volume, + SliceViewerWidget& widget, + VolumeProjection projection, + size_t layer) : + VolumeImageInteractor(broker, volume, widget, projection), + widget_(widget), + layer_(layer) + { + } + }; + + class RtViewerDemoApplication : + public RtViewerDemoBaseApplicationSingleCanvas, + public IObserver + { + public: + std::vector<std::pair<SliceViewerWidget*, size_t> > doseCtWidgetLayerPairs_; + std::list<OrthancStone::IWorldSceneInteractor*> interactors_; + + class Interactor : public IWorldSceneInteractor + { + private: + RtViewerDemoApplication& application_; + + public: + Interactor(RtViewerDemoApplication& application) : + application_(application) + { + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + IStatusBar* statusBar, + const std::vector<Touch>& displayTouches) + { + 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<SliceViewerWidget&>(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, + KeyboardKeys key, + char keyChar, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + switch (keyChar) + { + case 's': + // TODO: recursively traverse children + widget.FitContent(); + break; + + default: + break; + } + } + }; + + void OffsetSlice(int offset) + { + if (source_ != NULL) + { + int slice = static_cast<int>(slice_) + offset; + + if (slice < 0) + { + slice = 0; + } + + if (slice >= static_cast<int>(source_->GetSliceCount())) + { + slice = static_cast<int>(source_->GetSliceCount()) - 1; + } + + if (slice != static_cast<int>(slice_)) + { + SetSlice(slice); + } + } + } + + + SliceViewerWidget& GetMainWidget() + { + return *dynamic_cast<SliceViewerWidget*>(mainWidget_); + } + + + void SetSlice(size_t index) + { + if (source_ != NULL && + index < source_->GetSliceCount()) + { + slice_ = static_cast<unsigned int>(index); + +#if 1 + GetMainWidget().SetSlice(source_->GetSlice(slice_).GetGeometry()); +#else + // TEST for scene extents - Rotate the axes + double a = 15.0 / 180.0 * boost::math::constants::pi<double>(); + +#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 + } + } + + + void OnMainWidgetGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message) + { + // Once the geometry of the series is downloaded from Orthanc, + // display its middle slice, and adapt the viewport to fit this + // slice + if (source_ == &message.GetOrigin()) + { + SetSlice(source_->GetSliceCount() / 2); + } + + GetMainWidget().FitContent(); + } + + DicomFrameConverter converter_; + + void OnSliceContentChangedMessage(const ISlicedVolume::SliceContentChangedMessage& message) + { + converter_ = message.GetSlice().GetConverter(); + } + + void OnVolumeReadyMessage(const ISlicedVolume::VolumeReadyMessage& message) + { + const float percentile = 0.01f; + + auto& slicedVolume = message.GetOrigin(); + const OrthancVolumeImage& volume = dynamic_cast<const OrthancVolumeImage&>(slicedVolume); + + std::vector<float> distribution; + ReadDistribution(distribution, volume.GetImage().GetInternalImage()); + std::sort(distribution.begin(), distribution.end()); + + int start = static_cast<int>(std::ceil(distribution.size() * percentile)); + int end = static_cast<int>(std::floor(distribution.size() * (1.0f - percentile))); + + float a = 0; + float b = 0; + + if (start < end && + start >= 0 && + end < static_cast<int>(distribution.size())) + { + a = distribution[start]; + b = distribution[end]; + } + else if (!distribution.empty()) + { + // Too small distribution: Use full range + a = distribution.front(); + b = distribution.back(); + } + + //printf("WINDOWING %f %f\n", a, b); + + for (const auto& pair : doseCtWidgetLayerPairs_) + { + auto widget = pair.first; + auto layer = pair.second; + RenderStyle s = widget->GetLayerStyle(layer); + s.windowing_ = ImageWindowing_Custom; + s.customWindowCenter_ = static_cast<float>(converter_.Apply((a + b) / 2.0f)); + s.customWindowWidth_ = static_cast<float>(converter_.Apply(b - a)); + + // 96.210556 => 192.421112 + widget->SetLayerStyle(layer, s); + printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); + } + } + + + + size_t AddDoseLayer(SliceViewerWidget& widget, + OrthancVolumeImage& volume, VolumeProjection projection); + + void AddStructLayer( + SliceViewerWidget& widget, StructureSetLoader& loader); + + SliceViewerWidget* CreateDoseCtWidget( + std::auto_ptr<OrthancVolumeImage>& ct, + std::auto_ptr<OrthancVolumeImage>& dose, + std::auto_ptr<StructureSetLoader>& structLoader, + VolumeProjection projection); + + void AddCtLayer(SliceViewerWidget& widget, OrthancVolumeImage& volume); + + std::auto_ptr<Interactor> mainWidgetInteractor_; + const DicomSeriesVolumeSlicer* source_; + unsigned int slice_; + + std::string ctSeries_; + std::string doseInstance_; + std::string doseSeries_; + std::string structInstance_; + std::auto_ptr<OrthancStone::OrthancVolumeImage> dose_; + std::auto_ptr<OrthancStone::OrthancVolumeImage> ct_; + std::auto_ptr<OrthancStone::StructureSetLoader> struct_; + + public: + RtViewerDemoApplication(MessageBroker& broker) : + IObserver(broker), + source_(NULL), + slice_(0) + { + } + + /* + dev options on bgo xps15 + + COMMAND LINE + --ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 + + URL PARAMETERS + ?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 + + */ + + void ParseParameters(const boost::program_options::variables_map& parameters) + { + // CT series + { + + if (parameters.count("ct-series") != 1) + { + LOG(ERROR) << "There must be exactly one CT series specified"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + ctSeries_ = parameters["ct-series"].as<std::string>(); + } + + // RTDOSE + { + if (parameters.count("dose-instance") == 1) + { + doseInstance_ = parameters["dose-instance"].as<std::string>(); + } + else + { +#ifdef BGO_NOT_IMPLEMENTED_YET + // Dose series + if (parameters.count("dose-series") != 1) + { + LOG(ERROR) << "the RTDOSE series is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + doseSeries_ = parameters["ct"].as<std::string>(); +#endif + LOG(ERROR) << "the RTSTRUCT instance is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + // RTSTRUCT + { + if (parameters.count("struct-instance") == 1) + { + structInstance_ = parameters["struct-instance"].as<std::string>(); + } + else + { +#ifdef BGO_NOT_IMPLEMENTED_YET + // Struct series + if (parameters.count("struct-series") != 1) + { + LOG(ERROR) << "the RTSTRUCT series is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + structSeries_ = parametersstruct - series"].as<std::string>(); +#endif + LOG(ERROR) << "the RTSTRUCT instance is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + } + + virtual void DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("RtViewerDemo options. Please note that some of these options are mutually exclusive"); + generic.add_options() + ("ct-series", boost::program_options::value<std::string>(),"Orthanc ID of the CT series") + ("dose-instance", boost::program_options::value<std::string>(), "Orthanc ID of the RTDOSE instance (incompatible with dose-series)") + ("dose-series", boost::program_options::value<std::string>(), "NOT IMPLEMENTED YET. Orthanc ID of the RTDOSE series (incompatible with dose-instance)") + ("struct-instance", boost::program_options::value<std::string>(), "Orthanc ID of the RTSTRUCT instance (incompatible with struct-series)") + ("struct-series", boost::program_options::value<std::string>(), "NOT IMPLEMENTED YET. Orthanc ID of the RTSTRUCT (incompatible with struct-instance)") + ("smooth", boost::program_options::value<bool>()->default_value(true),"Enable bilinear image smoothing") + ; + + options.add(generic); + } + + virtual void Initialize( + StoneApplicationContext* context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + ParseParameters(parameters); + + context_ = context; + + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); + + if (!ctSeries_.empty()) + { + printf("CT = [%s]\n", ctSeries_.c_str()); + + ct_.reset(new OrthancStone::OrthancVolumeImage( + IObserver::GetBroker(), context->GetOrthancApiClient(), false)); + ct_->ScheduleLoadSeries(ctSeries_); + //ct_->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // IBA + //ct_->ScheduleLoadSeries("03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); // 0522c0001 TCIA + } + + if (!doseSeries_.empty() || + !doseInstance_.empty()) + { + dose_.reset(new OrthancStone::OrthancVolumeImage( + IObserver::GetBroker(), context->GetOrthancApiClient(), true)); + + + dose_->RegisterObserverCallback( + new Callable<RtViewerDemoApplication, ISlicedVolume::VolumeReadyMessage> + (*this, &RtViewerDemoApplication::OnVolumeReadyMessage)); + + dose_->RegisterObserverCallback( + new Callable<RtViewerDemoApplication, ISlicedVolume::SliceContentChangedMessage> + (*this, &RtViewerDemoApplication::OnSliceContentChangedMessage)); + + if (doseInstance_.empty()) + { + dose_->ScheduleLoadSeries(doseSeries_); + } + else + { + dose_->ScheduleLoadInstance(doseInstance_); + } + + //dose_->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1 + //dose_->ScheduleLoadInstance("269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c"); // 0522c0001 TCIA + } + + if (!structInstance_.empty()) + { + struct_.reset(new OrthancStone::StructureSetLoader( + IObserver::GetBroker(), context->GetOrthancApiClient())); + + struct_->ScheduleLoadInstance(structInstance_); + + //struct_->ScheduleLoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // IBA + //struct_->ScheduleLoadInstance("17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA + } + + mainWidget_ = new LayoutWidget("main-layout"); + mainWidget_->SetBackgroundColor(0, 0, 0); + mainWidget_->SetBackgroundCleared(true); + mainWidget_->SetPadding(0); + + auto axialWidget = CreateDoseCtWidget + (ct_, dose_, struct_, OrthancStone::VolumeProjection_Axial); + mainWidget_->AddWidget(axialWidget); + + std::auto_ptr<OrthancStone::LayoutWidget> subLayout( + new OrthancStone::LayoutWidget("main-layout")); + subLayout->SetVertical(); + subLayout->SetPadding(5); + + auto coronalWidget = CreateDoseCtWidget + (ct_, dose_, struct_, OrthancStone::VolumeProjection_Coronal); + subLayout->AddWidget(coronalWidget); + + auto sagittalWidget = CreateDoseCtWidget + (ct_, dose_, struct_, OrthancStone::VolumeProjection_Sagittal); + subLayout->AddWidget(sagittalWidget); + + mainWidget_->AddWidget(subLayout.release()); + } + }; + + + size_t RtViewerDemoApplication::AddDoseLayer( + SliceViewerWidget& widget, + OrthancVolumeImage& volume, VolumeProjection projection) + { + size_t layer = widget.AddLayer( + new VolumeImageMPRSlicer(IObserver::GetBroker(), volume)); + + RenderStyle s; + //s.drawGrid_ = true; + s.SetColor(255, 0, 0); // Draw missing PET layer in red + s.alpha_ = 0.3f; + s.applyLut_ = true; + s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; + s.interpolation_ = ImageInterpolation_Bilinear; + widget.SetLayerStyle(layer, s); + + return layer; + } + + void RtViewerDemoApplication::AddStructLayer( + SliceViewerWidget& widget, StructureSetLoader& loader) + { + widget.AddLayer(new DicomStructureSetSlicer( + IObserver::GetBroker(), loader)); + } + + SliceViewerWidget* RtViewerDemoApplication::CreateDoseCtWidget( + std::auto_ptr<OrthancVolumeImage>& ct, + std::auto_ptr<OrthancVolumeImage>& dose, + std::auto_ptr<StructureSetLoader>& structLoader, + VolumeProjection projection) + { + std::auto_ptr<OrthancStone::SliceViewerWidget> widget( + new OrthancStone::SliceViewerWidget(IObserver::GetBroker(), + "ct-dose-widget")); + + if (ct.get() != NULL) + { + AddCtLayer(*widget, *ct); + } + + if (dose.get() != NULL) + { + size_t layer = AddDoseLayer(*widget, *dose, projection); + + // we need to store the dose rendering widget because we'll update them + // according to various asynchronous events + doseCtWidgetLayerPairs_.push_back(std::make_pair(widget.get(), layer)); +#if 0 + interactors_.push_back(new VolumeImageInteractor( + IObserver::GetBroker(), *dose, *widget, projection)); +#else + interactors_.push_back(new DoseInteractor( + IObserver::GetBroker(), *dose, *widget, projection, layer)); +#endif + } + else if (ct.get() != NULL) + { + interactors_.push_back( + new VolumeImageInteractor( + IObserver::GetBroker(), *ct, *widget, projection)); + } + + if (structLoader.get() != NULL) + { + AddStructLayer(*widget, *structLoader); + } + + return widget.release(); + } + + void RtViewerDemoApplication::AddCtLayer( + SliceViewerWidget& widget, + OrthancVolumeImage& volume) + { + size_t layer = widget.AddLayer( + new VolumeImageMPRSlicer(IObserver::GetBroker(), volume)); + + RenderStyle s; + //s.drawGrid_ = true; + s.alpha_ = 1; + s.windowing_ = ImageWindowing_Bone; + widget.SetLayerStyle(layer, s); + } + } +} + + + +#if ORTHANC_ENABLE_WASM==1 + +#include "Platforms/Wasm/WasmWebService.h" +#include "Platforms/Wasm/WasmViewport.h" + +#include <emscripten/emscripten.h> + +//#include "SampleList.h" + + +OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) +{ + return new OrthancStone::Samples::RtViewerDemoApplication(broker); +} + +OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, OrthancStone::IStoneApplication* application) +{ + return dynamic_cast<OrthancStone::Samples::RtViewerDemoApplication*>(application)->CreateWasmApplicationAdapter(broker); +} + +#else + +//#include "SampleList.h" +#if ORTHANC_ENABLE_SDL==1 +#include "Applications/Sdl/SdlStoneApplicationRunner.h" +#endif +#if ORTHANC_ENABLE_QT==1 +#include "Applications/Qt/SampleQtApplicationRunner.h" +#endif +#include "Framework/Messages/MessageBroker.h" + +int main(int argc, char* argv[]) +{ + OrthancStone::MessageBroker broker; + OrthancStone::Samples::RtViewerDemoApplication sampleStoneApplication(broker); + +#if ORTHANC_ENABLE_SDL==1 + OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(broker, sampleStoneApplication); + return sdlApplicationRunner.Execute(argc, argv); +#endif +#if ORTHANC_ENABLE_QT==1 + OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication); + return qtAppRunner.Execute(argc, argv); +#endif +} + + +#endif + + + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/nginx.local.conf Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,44 @@ +# Local config to serve the WASM samples static files and reverse proxy Orthanc. +# Uses port 9977 instead of 80. + +# `events` section is mandatory +events { + worker_connections 1024; # Default: 1024 +} + +http { + + # prevent nginx sync issues on OSX + proxy_buffering off; + + server { + listen 9977 default_server; + client_max_body_size 4G; + + # location may have to be adjusted depending on your OS and nginx install + include /etc/nginx/mime.types; + # if not in your system mime.types, add this line to support WASM: + # types { + # application/wasm wasm; + # } + + # serve WASM static files + root build-web/; + location / { + } + + # reverse proxy orthanc + location /orthanc/ { + rewrite /orthanc(.*) $1 break; + proxy_pass http://127.0.0.1:8042; + proxy_set_header Host $http_host; + proxy_set_header my-auth-header good-token; + proxy_request_buffering off; + proxy_max_temp_file_size 0; + client_max_body_size 0; + } + + + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/rt-viewer-demo.html Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,25 @@ +<!doctype html> + +<html lang="us"> + <head> + <meta charset="utf-8" /> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + + <!-- Disable pinch zoom on mobile devices --> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> + <meta name="HandheldFriendly" content="true" /> + + <title>Simple Viewer</title> + <link href="samples-styles.css" rel="stylesheet" /> + +<body> + <div style="width: 100%; height: 5%"> + <p>RTSTRUCT viewer demonstration</p> + </div> + <div style="width: 100%; height: 95%"> + <canvas id="canvas"></canvas> + </div> + <script type="text/javascript" src="app-rt-viewer-demo.js"></script> +</body> + +</html> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/rt-viewer-demo.ts Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,5 @@ +import { InitializeWasmApplication } from '../../../Platforms/Wasm/wasm-application-runner'; + + +InitializeWasmApplication("RtViewerDemo", "/orthanc"); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/rt-viewer-demo.tsconfig.json Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,8 @@ +{ + "extends" : "./tsconfig-samples", + "compilerOptions": { + }, + "include" : [ + "rt-viewer-demo.ts" + ] +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/samples-styles.css Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,16 @@ +html, body { + width: 100%; + height: 100%; + margin: 0px; + border: 0; + overflow: hidden; /* Disable scrollbars */ + display: block; /* No floating content on sides */ + background-color: black; + color: white; + font-family: Arial, Helvetica, sans-serif; +} + +canvas { + left:0px; + top:0px; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/start-serving-files.sh Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,9 @@ +#!/bin/bash + +sudo nginx -p $(pwd) -c nginx.local.conf + +echo "Please browse to :" + +echo "http://localhost:9977/rt-viewer-demo.html?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9" + +echo "(This requires you have uploaded the correct files to your local Orthanc instance)"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/stop-serving-files.sh Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,4 @@ +#!/bin/bash + +sudo nginx -s stop +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/tsconfig-samples.json Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,11 @@ +{ + "extends" : "../../../Platforms/Wasm/tsconfig-stone.json", + "compilerOptions": { + "sourceMap": false, + "lib" : [ + "es2017", + "dom", + "dom.iterable" + ] + } +}
--- a/Applications/Sdl/SdlEngine.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/Sdl/SdlEngine.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -146,15 +146,15 @@ switch (event.button.button) { case SDL_BUTTON_LEFT: - locker.GetCentralViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers); + locker.GetCentralViewport().MouseDown(MouseButton_Left, event.button.x, event.button.y, modifiers, std::vector<Touch>()); break; case SDL_BUTTON_RIGHT: - locker.GetCentralViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers); + locker.GetCentralViewport().MouseDown(MouseButton_Right, event.button.x, event.button.y, modifiers, std::vector<Touch>()); break; case SDL_BUTTON_MIDDLE: - locker.GetCentralViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers); + locker.GetCentralViewport().MouseDown(MouseButton_Middle, event.button.x, event.button.y, modifiers, std::vector<Touch>()); break; default: @@ -163,7 +163,7 @@ } else if (event.type == SDL_MOUSEMOTION) { - locker.GetCentralViewport().MouseMove(event.button.x, event.button.y); + locker.GetCentralViewport().MouseMove(event.button.x, event.button.y, std::vector<Touch>()); } else if (event.type == SDL_MOUSEBUTTONUP) {
--- a/Applications/StoneApplicationContext.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Applications/StoneApplicationContext.h Thu Apr 18 09:30:00 2019 +0200 @@ -27,6 +27,7 @@ #include "../Framework/Viewport/WidgetViewport.h" + #else #include <list> namespace OrthancStone
--- a/Framework/Layers/CircleMeasureTracker.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Layers/CircleMeasureTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -91,7 +91,9 @@ void CircleMeasureTracker::MouseMove(int displayX, int displayY, double x, - double y) + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) { x2_ = x; y2_ = y;
--- a/Framework/Layers/CircleMeasureTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Layers/CircleMeasureTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -72,6 +72,8 @@ virtual void MouseMove(int displayX, int displayY, double x, - double y); + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); }; }
--- a/Framework/Layers/DicomSeriesVolumeSlicer.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Layers/DicomSeriesVolumeSlicer.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -93,10 +93,21 @@ loader_(broker, orthanc), quality_(SliceImageQuality_FullPng) { - loader_.RegisterObserverCallback(new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryReadyMessage>(*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady)); - loader_.RegisterObserverCallback(new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryErrorMessage>(*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError)); - loader_.RegisterObserverCallback(new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageReadyMessage>(*this, &DicomSeriesVolumeSlicer::OnSliceImageReady)); - loader_.RegisterObserverCallback(new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageErrorMessage>(*this, &DicomSeriesVolumeSlicer::OnSliceImageError)); + loader_.RegisterObserverCallback( + new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryReadyMessage> + (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady)); + + loader_.RegisterObserverCallback( + new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryErrorMessage> + (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError)); + + loader_.RegisterObserverCallback( + new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageReadyMessage> + (*this, &DicomSeriesVolumeSlicer::OnSliceImageReady)); + + loader_.RegisterObserverCallback( + new Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageErrorMessage> + (*this, &DicomSeriesVolumeSlicer::OnSliceImageError)); }
--- a/Framework/Layers/GrayscaleFrameRenderer.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Layers/GrayscaleFrameRenderer.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -121,8 +121,8 @@ bool isFullQuality) : FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality), frame_(Orthanc::Image::Clone(frame)), - defaultWindowCenter_(converter.GetDefaultWindowCenter()), - defaultWindowWidth_(converter.GetDefaultWindowWidth()), + defaultWindowCenter_(static_cast<float>(converter.GetDefaultWindowCenter())), + defaultWindowWidth_(static_cast<float>(converter.GetDefaultWindowWidth())), photometric_(converter.GetPhotometricInterpretation()) { if (frame_.get() == NULL)
--- a/Framework/Layers/LineMeasureTracker.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Layers/LineMeasureTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -87,7 +87,9 @@ void LineMeasureTracker::MouseMove(int displayX, int displayY, double x, - double y) + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) { x2_ = x; y2_ = y;
--- a/Framework/Layers/LineMeasureTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Layers/LineMeasureTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -71,6 +71,8 @@ virtual void MouseMove(int displayX, int displayY, double x, - double y); + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); }; }
--- a/Framework/Radiography/RadiographyAlphaLayer.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyAlphaLayer.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -45,6 +45,8 @@ SetSize(image->GetWidth(), image->GetHeight()); alpha_ = raii; + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyAlphaLayer::Render(Orthanc::ImageAccessor& buffer, @@ -84,7 +86,7 @@ if (useWindowing_) { float center, width; - if (scene_.GetWindowing(center, width)) + if (GetScene().GetWindowing(center, width)) { value = center + width / 2.0f; // set it to the maximum pixel value of the image }
--- a/Framework/Radiography/RadiographyAlphaLayer.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyAlphaLayer.h Thu Apr 18 09:30:00 2019 +0200 @@ -33,14 +33,13 @@ class RadiographyAlphaLayer : public RadiographyLayer { private: - const RadiographyScene& scene_; std::auto_ptr<Orthanc::ImageAccessor> alpha_; // Grayscale8 bool useWindowing_; float foreground_; public: - RadiographyAlphaLayer(const RadiographyScene& scene) : - scene_(scene), + RadiographyAlphaLayer(MessageBroker& broker, const RadiographyScene& scene) : + RadiographyLayer(broker, scene), useWindowing_(true), foreground_(0) {
--- a/Framework/Radiography/RadiographyDicomLayer.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -102,6 +102,8 @@ source_ = raii; ApplyConverter(); + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyDicomLayer::Render(Orthanc::ImageAccessor& buffer,
--- a/Framework/Radiography/RadiographyDicomLayer.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.h Thu Apr 18 09:30:00 2019 +0200 @@ -41,6 +41,11 @@ void ApplyConverter(); public: + RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene) + : RadiographyLayer(broker, scene) + { + } + void SetInstance(const std::string& instanceId, unsigned int frame) { instanceId_ = instanceId; @@ -61,6 +66,11 @@ void SetSourceImage(Orthanc::ImageAccessor* image); // Takes ownership + const Orthanc::ImageAccessor* GetSourceImage() const {return source_.get();} // currently need this access to serialize scene in plain old data to send to a WASM worker + + const DicomFrameConverter& GetDicomFrameConverter() const {return *converter_;} // currently need this access to serialize scene in plain old data to send to a WASM worker + void SetDicomFrameConverter(DicomFrameConverter* converter) {converter_.reset(converter);} // Takes ownership + virtual void Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, ImageInterpolation interpolation) const;
--- a/Framework/Radiography/RadiographyLayer.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyLayer.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -76,58 +76,14 @@ double x, double y) const { - transform_.Apply(x, y); + GetTransform().Apply(x, y); extent.AddPoint(x, y); } - - void RadiographyLayer::GetCornerInternal(double& x, - double& y, - Corner corner, - unsigned int cropX, - unsigned int cropY, - unsigned int cropWidth, - unsigned int cropHeight) const - { - double dx = static_cast<double>(cropX); - double dy = static_cast<double>(cropY); - double dwidth = static_cast<double>(cropWidth); - double dheight = static_cast<double>(cropHeight); - - switch (corner) - { - case Corner_TopLeft: - x = dx; - y = dy; - break; - - case Corner_TopRight: - x = dx + dwidth; - y = dy; - break; - - case Corner_BottomLeft: - x = dx; - y = dy + dheight; - break; - - case Corner_BottomRight: - x = dx + dwidth; - y = dy + dheight; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - transform_.Apply(x, y); - } - - bool RadiographyLayer::Contains(double x, double y) const { - transformInverse_.Apply(x, y); + GetTransformInverse().Apply(x, y); unsigned int cropX, cropY, cropWidth, cropHeight; GetCrop(cropX, cropY, cropWidth, cropHeight); @@ -140,53 +96,35 @@ void RadiographyLayer::DrawBorders(CairoContext& context, double zoom) { - unsigned int cx, cy, width, height; - GetCrop(cx, cy, width, height); - - double dx = static_cast<double>(cx); - double dy = static_cast<double>(cy); - double dwidth = static_cast<double>(width); - double dheight = static_cast<double>(height); + if (GetControlPointCount() < 3 ) + return; cairo_t* cr = context.GetObject(); cairo_set_line_width(cr, 2.0 / zoom); - double x, y; - x = dx; - y = dy; - transform_.Apply(x, y); - cairo_move_to(cr, x, y); - - x = dx + dwidth; - y = dy; - transform_.Apply(x, y); - cairo_line_to(cr, x, y); + ControlPoint cp; + GetControlPoint(cp, 0); + cairo_move_to(cr, cp.x, cp.y); - x = dx + dwidth; - y = dy + dheight; - transform_.Apply(x, y); - cairo_line_to(cr, x, y); + for (size_t i = 0; i < GetControlPointCount(); i++) + { + GetControlPoint(cp, i); + cairo_line_to(cr, cp.x, cp.y); + } - x = dx; - y = dy + dheight; - transform_.Apply(x, y); - cairo_line_to(cr, x, y); - - x = dx; - y = dy; - transform_.Apply(x, y); - cairo_line_to(cr, x, y); - + cairo_close_path(cr); cairo_stroke(cr); } - RadiographyLayer::RadiographyLayer() : + RadiographyLayer::RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene) : + IObservable(broker), index_(0), hasSize_(false), width_(0), height_(0), - prefferedPhotometricDisplayMode_(PhotometricDisplayMode_Default) + prefferedPhotometricDisplayMode_(PhotometricDisplayMode_Default), + scene_(scene) { UpdateTransform(); } @@ -197,6 +135,13 @@ UpdateTransform(); } + void RadiographyLayer::SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode prefferedPhotometricDisplayMode) + { + prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode; + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + } + void RadiographyLayer::SetCrop(unsigned int x, unsigned int y, unsigned int width, @@ -215,6 +160,8 @@ geometry_.SetCrop(x, y, width, height); UpdateTransform(); + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyLayer::SetGeometry(const Geometry& geometry) @@ -225,6 +172,8 @@ { UpdateTransform(); } + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -237,7 +186,7 @@ { GetGeometry().GetCrop(x, y, width, height); } - else + else { x = 0; y = 0; @@ -251,6 +200,8 @@ { geometry_.SetAngle(angle); UpdateTransform(); + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -269,6 +220,7 @@ height_ = height; UpdateTransform(); + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -305,7 +257,7 @@ } else { - transformInverse_.Apply(sceneX, sceneY); + GetTransformInverse().Apply(sceneX, sceneY); int x = static_cast<int>(std::floor(sceneX)); int y = static_cast<int>(std::floor(sceneY)); @@ -346,6 +298,7 @@ { geometry_.SetPan(x, y); UpdateTransform(); + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -354,6 +307,7 @@ { geometry_.SetPixelSpacing(x, y); UpdateTransform(); + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -362,48 +316,65 @@ { centerX = static_cast<double>(width_) / 2.0; centerY = static_cast<double>(height_) / 2.0; - transform_.Apply(centerX, centerY); - } - - - void RadiographyLayer::GetCorner(double& x /* out */, - double& y /* out */, - Corner corner) const - { - unsigned int cropX, cropY, cropWidth, cropHeight; - GetCrop(cropX, cropY, cropWidth, cropHeight); - GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight); + GetTransform().Apply(centerX, centerY); } - bool RadiographyLayer::LookupCorner(Corner& corner /* out */, - double x, - double y, - double zoom, - double viewportDistance) const + + size_t RadiographyLayer::GetControlPointCount() const {return 4;} + + void RadiographyLayer::GetControlPoint(ControlPoint& cpScene /* out in scene coordinates */, + size_t index) const { - static const Corner CORNERS[] = { - Corner_TopLeft, - Corner_TopRight, - Corner_BottomLeft, - Corner_BottomRight - }; - unsigned int cropX, cropY, cropWidth, cropHeight; GetCrop(cropX, cropY, cropWidth, cropHeight); + ControlPoint cp; + switch (index) + { + case ControlPoint_TopLeftCorner: + cp = ControlPoint(cropX, cropY, ControlPoint_TopLeftCorner); + break; + + case ControlPoint_TopRightCorner: + cp = ControlPoint(cropX + cropWidth, cropY, ControlPoint_TopRightCorner); + break; + + case ControlPoint_BottomLeftCorner: + cp = ControlPoint(cropX, cropY + cropHeight, ControlPoint_BottomLeftCorner); + break; + + case ControlPoint_BottomRightCorner: + cp = ControlPoint(cropX + cropWidth, cropY + cropHeight, ControlPoint_BottomRightCorner); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + // transforms image coordinates into scene coordinates + GetTransform().Apply(cp.x, cp.y); + cpScene = cp; + } + + bool RadiographyLayer::LookupControlPoint(ControlPoint& cpScene /* out */, + double x, + double y, + double zoom, + double viewportDistance) const + { double threshold = Square(viewportDistance / zoom); - for (size_t i = 0; i < 4; i++) + for (size_t i = 0; i < GetControlPointCount(); i++) { - double cx, cy; - GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight); + ControlPoint cp; + GetControlPoint(cp, i); - double d = Square(cx - x) + Square(cy - y); + double d = Square(cp.x - x) + Square(cp.y - y); if (d <= threshold) { - corner = CORNERS[i]; + cpScene = cp; return true; } }
--- a/Framework/Radiography/RadiographyLayer.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyLayer.h Thu Apr 18 09:30:00 2019 +0200 @@ -24,14 +24,50 @@ #include "../Toolbox/AffineTransform2D.h" #include "../Toolbox/Extent2D.h" #include "../Viewport/CairoContext.h" +#include "../Messages/IMessage.h" +#include "../Messages/IObservable.h" namespace OrthancStone { - class RadiographyLayer : public boost::noncopyable + class RadiographyScene; + + struct ControlPoint + { + double x; + double y; + size_t index; + + ControlPoint(double x, double y, size_t index) + : x(x), + y(y), + index(index) + {} + + ControlPoint() + : x(0), + y(0), + index(std::numeric_limits<size_t>::max()) + {} + }; + + class RadiographyLayer : public IObservable { friend class RadiographyScene; public: + class LayerEditedMessage : + public OriginMessage<MessageType_RadiographyLayer_Edited, RadiographyLayer> + { + private: + + public: + LayerEditedMessage(const RadiographyLayer& origin) : + OriginMessage(origin) + { + } + }; + + class Geometry { bool hasCrop_; @@ -141,47 +177,41 @@ AffineTransform2D transformInverse_; Geometry geometry_; PhotometricDisplayMode prefferedPhotometricDisplayMode_; - + const RadiographyScene& scene_; protected: - const AffineTransform2D& GetTransform() const + virtual const AffineTransform2D& GetTransform() const { return transform_; } - void SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode prefferedPhotometricDisplayMode) + virtual const AffineTransform2D& GetTransformInverse() const { - prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode; + return transformInverse_; } + void SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode prefferedPhotometricDisplayMode); + private: void UpdateTransform(); - + void AddToExtent(Extent2D& extent, double x, double y) const; - void GetCornerInternal(double& x, - double& y, - Corner corner, - unsigned int cropX, - unsigned int cropY, - unsigned int cropWidth, - unsigned int cropHeight) const; - void SetIndex(size_t index) { index_ = index; } - + bool Contains(double x, double y) const; - + void DrawBorders(CairoContext& context, double zoom); public: - RadiographyLayer(); + RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene); virtual ~RadiographyLayer() { @@ -192,6 +222,11 @@ return index_; } + const RadiographyScene& GetScene() const + { + return scene_; + } + const Geometry& GetGeometry() const { return geometry_; @@ -232,19 +267,19 @@ unsigned int GetWidth() const { return width_; - } + } unsigned int GetHeight() const { return height_; - } + } Extent2D GetExtent() const; - bool GetPixel(unsigned int& imageX, - unsigned int& imageY, - double sceneX, - double sceneY) const; + virtual bool GetPixel(unsigned int& imageX, + unsigned int& imageY, + double sceneX, + double sceneY) const; void SetPixelSpacing(double x, double y); @@ -252,15 +287,16 @@ void GetCenter(double& centerX, double& centerY) const; - void GetCorner(double& x /* out */, - double& y /* out */, - Corner corner) const; - - bool LookupCorner(Corner& corner /* out */, - double x, - double y, - double zoom, - double viewportDistance) const; + virtual void GetControlPoint(ControlPoint& cpScene /* out in scene coordinates */, + size_t index) const; + + virtual size_t GetControlPointCount() const; + + bool LookupControlPoint(ControlPoint& cpScene /* out */, + double x, + double y, + double zoom, + double viewportDistance) const; virtual bool GetDefaultWindowing(float& center, float& width) const = 0; @@ -276,5 +312,7 @@ virtual bool GetRange(float& minValue, float& maxValue) const = 0; - }; + + friend class RadiographyMaskLayer; // because it needs to GetTransform on the dicomLayer it relates to + }; }
--- a/Framework/Radiography/RadiographyLayerCropTracker.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerCropTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -68,12 +68,10 @@ RadiographyScene& scene, const ViewportGeometry& view, size_t layer, - double x, - double y, - Corner corner) : + const ControlPoint& startControlPoint) : undoRedoStack_(undoRedoStack), accessor_(scene, layer), - corner_(corner) + startControlPoint_(startControlPoint) { if (accessor_.IsValid()) { @@ -101,7 +99,9 @@ void RadiographyLayerCropTracker::MouseMove(int displayX, int displayY, double sceneX, - double sceneY) + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) { if (accessor_.IsValid()) { @@ -112,8 +112,8 @@ { unsigned int targetX, targetWidth; - if (corner_ == Corner_TopLeft || - corner_ == Corner_BottomLeft) + if (startControlPoint_.index == ControlPoint_TopLeftCorner || + startControlPoint_.index == ControlPoint_BottomLeftCorner) { targetX = std::min(x, cropX_ + cropWidth_); targetWidth = cropX_ + cropWidth_ - targetX; @@ -126,8 +126,8 @@ unsigned int targetY, targetHeight; - if (corner_ == Corner_TopLeft || - corner_ == Corner_TopRight) + if (startControlPoint_.index == ControlPoint_TopLeftCorner || + startControlPoint_.index == ControlPoint_TopRightCorner) { targetY = std::min(y, cropY_ + cropHeight_); targetHeight = cropY_ + cropHeight_ - targetY;
--- a/Framework/Radiography/RadiographyLayerCropTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerCropTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -35,7 +35,7 @@ UndoRedoStack& undoRedoStack_; RadiographyScene::LayerAccessor accessor_; - Corner corner_; + ControlPoint startControlPoint_; unsigned int cropX_; unsigned int cropY_; unsigned int cropWidth_; @@ -46,9 +46,7 @@ RadiographyScene& scene, const ViewportGeometry& view, size_t layer, - double x, - double y, - Corner corner); + const ControlPoint& startControlPoint); virtual bool HasRender() const { @@ -63,6 +61,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY); + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Radiography/RadiographyLayerMaskTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,140 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "RadiographyLayerMaskTracker.h" +#include "RadiographyMaskLayer.h" + +#include "RadiographySceneCommand.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + class RadiographyLayerMaskTracker::UndoRedoCommand : public RadiographySceneCommand + { + private: + ControlPoint sourceSceneCp_; + ControlPoint targetSceneCp_; + + protected: + virtual void UndoInternal(RadiographyLayer& layer) const + { + RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer); + if (maskLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + unsigned int ix, iy; // image coordinates + if (maskLayer->GetPixel(ix, iy, sourceSceneCp_.x, sourceSceneCp_.y)) + { + maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), sourceSceneCp_.index); + } + } + + virtual void RedoInternal(RadiographyLayer& layer) const + { + RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer); + if (maskLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + unsigned int ix, iy; // image coordinates + if (maskLayer->GetPixel(ix, iy, targetSceneCp_.x, targetSceneCp_.y)) + { + maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), targetSceneCp_.index); + } + } + + public: + UndoRedoCommand(const RadiographyLayerMaskTracker& tracker) : + RadiographySceneCommand(tracker.accessor_), + sourceSceneCp_(tracker.startSceneCp_), + targetSceneCp_(tracker.endSceneCp_) + { + RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&(tracker.accessor_.GetLayer())); + if (maskLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + unsigned int ix, iy; // image coordinates + if (maskLayer->GetPixel(ix, iy, targetSceneCp_.x, targetSceneCp_.y)) + { + maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), targetSceneCp_.index); + } + } + }; + + + RadiographyLayerMaskTracker::RadiographyLayerMaskTracker(UndoRedoStack& undoRedoStack, + RadiographyScene& scene, + const ViewportGeometry& view, + size_t layer, + const ControlPoint& startSceneControlPoint) : + undoRedoStack_(undoRedoStack), + accessor_(scene, layer), + startSceneCp_(startSceneControlPoint), + endSceneCp_(startSceneControlPoint) + { + } + + + void RadiographyLayerMaskTracker::Render(CairoContext& context, + double zoom) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + + void RadiographyLayerMaskTracker::MouseUp() + { + if (accessor_.IsValid() && startSceneCp_.x != endSceneCp_.x && startSceneCp_.y != endSceneCp_.y) + { + undoRedoStack_.Add(new UndoRedoCommand(*this)); + } + } + + + void RadiographyLayerMaskTracker::MouseMove(int displayX, + int displayY, + double sceneX, + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) + { + if (accessor_.IsValid()) + { + unsigned int ix, iy; // image coordinates + + RadiographyLayer& layer = accessor_.GetLayer(); + if (layer.GetPixel(ix, iy, sceneX, sceneY)) + { + endSceneCp_ = ControlPoint(sceneX, sceneY, startSceneCp_.index); + + RadiographyMaskLayer* maskLayer = dynamic_cast<RadiographyMaskLayer*>(&layer); + if (maskLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + maskLayer->SetCorner(Orthanc::ImageProcessing::ImagePoint((int32_t)ix, (int32_t)iy), startSceneCp_.index); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Radiography/RadiographyLayerMaskTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,65 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Toolbox/UndoRedoStack.h" +#include "../Toolbox/ViewportGeometry.h" +#include "../Widgets/IWorldSceneMouseTracker.h" +#include "RadiographyScene.h" + +namespace OrthancStone +{ + class RadiographyLayerMaskTracker : public IWorldSceneMouseTracker + { + private: + class UndoRedoCommand; + + UndoRedoStack& undoRedoStack_; + RadiographyScene::LayerAccessor accessor_; + ControlPoint startSceneCp_; + ControlPoint endSceneCp_; + + public: + RadiographyLayerMaskTracker(UndoRedoStack& undoRedoStack, + RadiographyScene& scene, + const ViewportGeometry& view, + size_t layer, + const ControlPoint& startSceneControlPoint); + + virtual bool HasRender() const + { + return false; + } + + virtual void Render(CairoContext& context, + double zoom); + + virtual void MouseUp(); + + virtual void MouseMove(int displayX, + int displayY, + double sceneX, + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); + }; +}
--- a/Framework/Radiography/RadiographyLayerMoveTracker.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerMoveTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -97,7 +97,9 @@ void RadiographyLayerMoveTracker::MouseMove(int displayX, int displayY, double sceneX, - double sceneY) + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) { if (accessor_.IsValid()) {
--- a/Framework/Radiography/RadiographyLayerMoveTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerMoveTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -61,6 +61,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY); + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); }; }
--- a/Framework/Radiography/RadiographyLayerResizeTracker.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerResizeTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -85,9 +85,7 @@ RadiographyLayerResizeTracker::RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack, RadiographyScene& scene, size_t layer, - double x, - double y, - Corner corner, + const ControlPoint& startControlPoint, bool roundScaling) : undoRedoStack_(undoRedoStack), accessor_(scene, layer), @@ -101,31 +99,32 @@ originalPanX_ = accessor_.GetLayer().GetGeometry().GetPanX(); originalPanY_ = accessor_.GetLayer().GetGeometry().GetPanY(); - switch (corner) + size_t oppositeControlPointType; + switch (startControlPoint.index) { - case Corner_TopLeft: - oppositeCorner_ = Corner_BottomRight; + case ControlPoint_TopLeftCorner: + oppositeControlPointType = ControlPoint_BottomRightCorner; break; - case Corner_TopRight: - oppositeCorner_ = Corner_BottomLeft; + case ControlPoint_TopRightCorner: + oppositeControlPointType = ControlPoint_BottomLeftCorner; break; - case Corner_BottomLeft: - oppositeCorner_ = Corner_TopRight; + case ControlPoint_BottomLeftCorner: + oppositeControlPointType = ControlPoint_TopRightCorner; break; - case Corner_BottomRight: - oppositeCorner_ = Corner_TopLeft; + case ControlPoint_BottomRightCorner: + oppositeControlPointType = ControlPoint_TopLeftCorner; break; default: throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - accessor_.GetLayer().GetCorner(oppositeX_, oppositeY_, oppositeCorner_); + accessor_.GetLayer().GetControlPoint(startOppositeControlPoint_, oppositeControlPointType); - double d = ComputeDistance(x, y, oppositeX_, oppositeY_); + double d = ComputeDistance(startControlPoint.x, startControlPoint.y, startOppositeControlPoint_.x, startOppositeControlPoint_.y); if (d >= std::numeric_limits<float>::epsilon()) { baseScaling_ = 1.0 / d; @@ -159,14 +158,16 @@ void RadiographyLayerResizeTracker::MouseMove(int displayX, int displayY, double sceneX, - double sceneY) + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) { static const double ROUND_SCALING = 0.1; if (accessor_.IsValid() && accessor_.GetLayer().GetGeometry().IsResizeable()) { - double scaling = ComputeDistance(oppositeX_, oppositeY_, sceneX, sceneY) * baseScaling_; + double scaling = ComputeDistance(startOppositeControlPoint_.x, startOppositeControlPoint_.y, sceneX, sceneY) * baseScaling_; if (roundScaling_) { @@ -178,10 +179,10 @@ scaling * originalSpacingY_); // Keep the opposite corner at a fixed location - double ox, oy; - layer.GetCorner(ox, oy, oppositeCorner_); - layer.SetPan(layer.GetGeometry().GetPanX() + oppositeX_ - ox, - layer.GetGeometry().GetPanY() + oppositeY_ - oy); + ControlPoint currentOppositeCorner; + layer.GetControlPoint(currentOppositeCorner, startOppositeControlPoint_.index); + layer.SetPan(layer.GetGeometry().GetPanX() + startOppositeControlPoint_.x - currentOppositeCorner.x, + layer.GetGeometry().GetPanY() + startOppositeControlPoint_.y - currentOppositeCorner.y); } } }
--- a/Framework/Radiography/RadiographyLayerResizeTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerResizeTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -39,18 +39,14 @@ double originalSpacingY_; double originalPanX_; double originalPanY_; - Corner oppositeCorner_; - double oppositeX_; - double oppositeY_; + ControlPoint startOppositeControlPoint_; double baseScaling_; public: RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack, RadiographyScene& scene, size_t layer, - double x, - double y, - Corner corner, + const ControlPoint& startControlPoint, bool roundScaling); virtual bool HasRender() const @@ -66,6 +62,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY); + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); }; }
--- a/Framework/Radiography/RadiographyLayerRotateTracker.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerRotateTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -132,7 +132,9 @@ void RadiographyLayerRotateTracker::MouseMove(int displayX, int displayY, double sceneX, - double sceneY) + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) { static const double ROUND_ANGLE = 15.0 / 180.0 * boost::math::constants::pi<double>();
--- a/Framework/Radiography/RadiographyLayerRotateTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerRotateTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -68,6 +68,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY); + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Radiography/RadiographyMaskLayer.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,143 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "RadiographyMaskLayer.h" +#include "RadiographyDicomLayer.h" + +#include "RadiographyScene.h" +#include "Core/Images/Image.h" +#include "Core/Images/ImageProcessing.h" +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + const unsigned char IN_MASK_VALUE = 0x00; + const unsigned char OUT_MASK_VALUE = 0xFF; + + const AffineTransform2D& RadiographyMaskLayer::GetTransform() const + { + return dicomLayer_.GetTransform(); + } + + const AffineTransform2D& RadiographyMaskLayer::GetTransformInverse() const + { + return dicomLayer_.GetTransformInverse(); + } + + bool RadiographyMaskLayer::GetPixel(unsigned int& imageX, + unsigned int& imageY, + double sceneX, + double sceneY) const + { + return dicomLayer_.GetPixel(imageX, imageY, sceneX, sceneY); + } + + std::string RadiographyMaskLayer::GetInstanceId() const + { + return dicomLayer_.GetInstanceId(); + } + + void RadiographyMaskLayer::SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index) + { + if (index < corners_.size()) + corners_[index] = corner; + else + corners_.push_back(corner); + invalidated_ = true; + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + } + + void RadiographyMaskLayer::SetCorners(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners) + { + corners_ = corners; + invalidated_ = true; + + EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + } + + void RadiographyMaskLayer::Render(Orthanc::ImageAccessor& buffer, + const AffineTransform2D& viewTransform, + ImageInterpolation interpolation) const + { + if (dicomLayer_.GetWidth() == 0) // nothing to do if the DICOM layer is not displayed (or not loaded) + return; + + if (invalidated_) + { + mask_.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, dicomLayer_.GetWidth(), dicomLayer_.GetHeight(), false)); + + DrawMask(); + + invalidated_ = false; + } + + {// rendering + if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + unsigned int cropX, cropY, cropWidth, cropHeight; + dicomLayer_.GetCrop(cropX, cropY, cropWidth, cropHeight); + + const AffineTransform2D t = AffineTransform2D::Combine( + viewTransform, dicomLayer_.GetTransform(), + AffineTransform2D::CreateOffset(cropX, cropY)); + + Orthanc::ImageAccessor cropped; + mask_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); + + Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); + + t.Apply(tmp, cropped, interpolation, true /* clear */); + + // Blit + const unsigned int width = buffer.GetWidth(); + const unsigned int height = buffer.GetHeight(); + + for (unsigned int y = 0; y < height; y++) + { + float *q = reinterpret_cast<float*>(buffer.GetRow(y)); + const uint8_t *p = reinterpret_cast<uint8_t*>(tmp.GetRow(y)); + + for (unsigned int x = 0; x < width; x++, p++, q++) + { + if (*p == OUT_MASK_VALUE) + *q = foreground_; + // else keep the underlying pixel value + } + } + + } + } + + void RadiographyMaskLayer::DrawMask() const + { + // first fill the complete image + Orthanc::ImageProcessing::Set(*mask_, OUT_MASK_VALUE); + + // fill mask + Orthanc::ImageProcessing::FillPolygon(*mask_, corners_, IN_MASK_VALUE); + + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Radiography/RadiographyMaskLayer.h Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,127 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "RadiographyLayer.h" +#include "Core/Images/Image.h" +#include "Core/Images/ImageProcessing.h" + +namespace OrthancStone +{ + class RadiographyScene; + class RadiographyDicomLayer; + + class RadiographyMaskLayer : public RadiographyLayer + { + private: + std::vector<Orthanc::ImageProcessing::ImagePoint> corners_; + const RadiographyDicomLayer& dicomLayer_; + mutable bool invalidated_; + float foreground_; + + mutable std::auto_ptr<Orthanc::ImageAccessor> mask_; + public: + RadiographyMaskLayer(MessageBroker& broker, const RadiographyScene& scene, const RadiographyDicomLayer& dicomLayer, + float foreground) : + RadiographyLayer(broker, scene), + dicomLayer_(dicomLayer), + invalidated_(true), + foreground_(foreground) + { + } + + void SetCorners(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners); + void SetCorner(const Orthanc::ImageProcessing::ImagePoint& corner, size_t index); + + const std::vector<Orthanc::ImageProcessing::ImagePoint>& GetCorners() const + { + return corners_; + } + + float GetForeground() const + { + return foreground_; + } + + virtual void Render(Orthanc::ImageAccessor& buffer, + const AffineTransform2D& viewTransform, + ImageInterpolation interpolation) const; + + std::string GetInstanceId() const; + + virtual size_t GetControlPointCount() const + { + return corners_.size(); + } + + virtual void GetControlPoint(ControlPoint& cpScene, + size_t index) const + { + ControlPoint cp(corners_[index].GetX(), corners_[index].GetY(), index); + + // transforms image coordinates into scene coordinates + GetTransform().Apply(cp.x, cp.y); + cpScene = cp; + } + + virtual bool GetDefaultWindowing(float& center, + float& width) const + { + return false; + } + + virtual bool GetRange(float& minValue, + float& maxValue) const + { + minValue = 0; + maxValue = 0; + + if (foreground_ < 0) + { + minValue = foreground_; + } + + if (foreground_ > 0) + { + maxValue = foreground_; + } + + return true; + + } + + virtual bool GetPixel(unsigned int& imageX, + unsigned int& imageY, + double sceneX, + double sceneY) const; + + protected: + virtual const AffineTransform2D& GetTransform() const; + + virtual const AffineTransform2D& GetTransformInverse() const; + + + private: + void DrawMask() const; + + }; +}
--- a/Framework/Radiography/RadiographyScene.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -24,6 +24,7 @@ #include "RadiographyAlphaLayer.h" #include "RadiographyDicomLayer.h" #include "RadiographyTextLayer.h" +#include "RadiographyMaskLayer.h" #include "../Toolbox/DicomFrameConverter.h" #include <Core/Images/Image.h> @@ -141,10 +142,15 @@ EmitMessage(GeometryChangedMessage(*this, *layer)); EmitMessage(ContentChangedMessage(*this, *layer)); + layer->RegisterObserverCallback(new Callable<RadiographyScene, RadiographyLayer::LayerEditedMessage>(*this, &RadiographyScene::OnLayerEdited)); return *layer; } + void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message) + { + EmitMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin())); + } RadiographyScene::RadiographyScene(MessageBroker& broker) : IObserver(broker), @@ -246,6 +252,8 @@ hasWindowing_ = true; windowingCenter_ = center; windowingWidth_ = width; + + EmitMessage(RadiographyScene::WindowingChangedMessage(*this)); } @@ -253,7 +261,7 @@ const std::string& utf8, RadiographyLayer::Geometry* geometry) { - std::auto_ptr<RadiographyTextLayer> alpha(new RadiographyTextLayer(*this)); + std::auto_ptr<RadiographyTextLayer> alpha(new RadiographyTextLayer(IObservable::GetBroker(), *this)); alpha->LoadText(font, utf8); if (geometry != NULL) { @@ -292,9 +300,25 @@ return LoadAlphaBitmap(block.release(), geometry); } + RadiographyLayer& RadiographyScene::LoadMask(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners, + const RadiographyDicomLayer& dicomLayer, + float foreground, + RadiographyLayer::Geometry* geometry) + { + std::auto_ptr<RadiographyMaskLayer> mask(new RadiographyMaskLayer(IObservable::GetBroker(), *this, dicomLayer, foreground)); + mask->SetCorners(corners); + if (geometry != NULL) + { + mask->SetGeometry(*geometry); + } + + return RegisterLayer(mask.release()); + } + + RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry) { - std::auto_ptr<RadiographyAlphaLayer> alpha(new RadiographyAlphaLayer(*this)); + std::auto_ptr<RadiographyAlphaLayer> alpha(new RadiographyAlphaLayer(IObservable::GetBroker(), *this)); alpha->SetAlpha(bitmap); if (geometry != NULL) { @@ -304,13 +328,36 @@ return RegisterLayer(alpha.release()); } + RadiographyLayer& RadiographyScene::LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership + const std::string& instance, + unsigned int frame, + DicomFrameConverter* converter, // takes ownership + PhotometricDisplayMode preferredPhotometricDisplayMode, + RadiographyLayer::Geometry* geometry) + { + RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this))); + + layer.SetInstance(instance, frame); + + if (geometry != NULL) + { + layer.SetGeometry(*geometry); + } + + layer.SetDicomFrameConverter(converter); + layer.SetSourceImage(dicomImage); + layer.SetPreferredPhotomotricDisplayMode(preferredPhotometricDisplayMode); + + return layer; + } + RadiographyLayer& RadiographyScene::LoadDicomFrame(OrthancApiClient& orthanc, const std::string& instance, unsigned int frame, bool httpCompression, RadiographyLayer::Geometry* geometry) { - RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer)); + RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this))); layer.SetInstance(instance, frame); if (geometry != NULL) @@ -354,7 +401,7 @@ RadiographyLayer& RadiographyScene::LoadDicomWebFrame(IWebService& web) { - RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer); + RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this)); return layer; @@ -522,41 +569,11 @@ } } - - void RadiographyScene::ExportDicom(OrthancApiClient& orthanc, - const Orthanc::DicomMap& dicom, - const std::string& parentOrthancId, - double pixelSpacingX, - double pixelSpacingY, - bool invert, - ImageInterpolation interpolation, - bool usePam) - { - Json::Value createDicomRequestContent; - - ExportToCreateDicomRequest(createDicomRequestContent, dicom, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam); - - if (!parentOrthancId.empty()) - { - createDicomRequestContent["Parent"] = parentOrthancId; - } - - orthanc.PostJsonAsyncExpectJson( - "/tools/create-dicom", createDicomRequestContent, - new Callable<RadiographyScene, OrthancApiClient::JsonResponseReadyMessage> - (*this, &RadiographyScene::OnDicomExported), - NULL, NULL); - } - - // Export using PAM is faster than using PNG, but requires Orthanc - // core >= 1.4.3 - void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent, - const Orthanc::DicomMap& dicom, - double pixelSpacingX, - double pixelSpacingY, - bool invert, - ImageInterpolation interpolation, - bool usePam) + Orthanc::Image* RadiographyScene::ExportToImage(double pixelSpacingX, + double pixelSpacingY, + ImageInterpolation interpolation, + bool invert, + int64_t maxValue /* for inversion */) { if (pixelSpacingX <= 0 || pixelSpacingY <= 0) @@ -564,12 +581,10 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - LOG(INFO) << "Exporting DICOM"; - Extent2D extent = GetSceneExtent(); - int w = std::ceil(extent.GetWidth() / pixelSpacingX); - int h = std::ceil(extent.GetHeight() / pixelSpacingY); + int w = static_cast<int>(std::ceil(extent.GetWidth() / pixelSpacingX)); + int h = static_cast<int>(std::ceil(extent.GetHeight() / pixelSpacingY)); if (w < 0 || h < 0) { @@ -589,9 +604,30 @@ Render(layers, view, interpolation); - Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, - layers.GetWidth(), layers.GetHeight(), false); - Orthanc::ImageProcessing::Convert(rendered, layers); + std::auto_ptr<Orthanc::Image> rendered(new Orthanc::Image(Orthanc::PixelFormat_Grayscale16, + layers.GetWidth(), layers.GetHeight(), false)); + + Orthanc::ImageProcessing::Convert(*rendered, layers); + if (invert) + Orthanc::ImageProcessing::Invert(*rendered, maxValue); + + return rendered.release(); + } + + + void RadiographyScene::ExportToCreateDicomRequest(Json::Value& createDicomRequestContent, + const Json::Value& dicomTags, + const std::string& parentOrthancId, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation, + bool usePam) + { + LOG(INFO) << "Exporting RadiographyScene to DICOM"; + VLOG(1) << "Exporting RadiographyScene to: export to image"; + + std::auto_ptr<Orthanc::Image> rendered(ExportToImage(pixelSpacingX, pixelSpacingY, interpolation)); // note: we don't invert the image in the pixels data because we'll set the PhotometricDisplayMode correctly in the DICOM tags std::string base64; @@ -600,43 +636,31 @@ if (usePam) { + VLOG(1) << "Exporting RadiographyScene: convert to PAM"; Orthanc::PamWriter writer; - writer.WriteToMemory(content, rendered); + writer.WriteToMemory(content, *rendered); } else { Orthanc::PngWriter writer; - writer.WriteToMemory(content, rendered); + writer.WriteToMemory(content, *rendered); } + VLOG(1) << "Exporting RadiographyScene: encoding to base64"; Orthanc::Toolbox::EncodeBase64(base64, content); } - std::set<Orthanc::DicomTag> tags; - dicom.GetTags(tags); - - createDicomRequestContent["Tags"] = Json::objectValue; - - for (std::set<Orthanc::DicomTag>::const_iterator - tag = tags.begin(); tag != tags.end(); ++tag) - { - const Orthanc::DicomValue& value = dicom.GetValue(*tag); - if (!value.IsNull() && - !value.IsBinary()) - { - createDicomRequestContent["Tags"][tag->Format()] = value.GetContent(); - } - } + createDicomRequestContent["Tags"] = dicomTags; PhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode(); if ((invert && photometricMode != PhotometricDisplayMode_Monochrome2) || (!invert && photometricMode == PhotometricDisplayMode_Monochrome1)) { - createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = "MONOCHROME1"; + createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME1"; } else { - createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = "MONOCHROME2"; + createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME2"; } // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to @@ -646,15 +670,15 @@ char buf[32]; sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX); - createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; + createDicomRequestContent["Tags"]["PixelSpacing"] = buf; float center, width; if (GetWindowing(center, width)) { - createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] = + createDicomRequestContent["Tags"]["WindowCenter"] = boost::lexical_cast<std::string>(boost::math::iround(center)); - createDicomRequestContent["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] = + createDicomRequestContent["Tags"]["WindowWidth"] = boost::lexical_cast<std::string>(boost::math::iround(width)); } @@ -663,9 +687,68 @@ createDicomRequestContent["Content"] = ("data:" + std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) + ";base64," + base64); + + if (!parentOrthancId.empty()) + { + createDicomRequestContent["Parent"] = parentOrthancId; + } + + VLOG(1) << "Exporting RadiographyScene: create-dicom request is ready"; + } + + + void RadiographyScene::ExportDicom(OrthancApiClient& orthanc, + const Json::Value& dicomTags, + const std::string& parentOrthancId, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation, + bool usePam) + { + Json::Value createDicomRequestContent; + + ExportToCreateDicomRequest(createDicomRequestContent, dicomTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam); + + orthanc.PostJsonAsyncExpectJson( + "/tools/create-dicom", createDicomRequestContent, + new Callable<RadiographyScene, OrthancApiClient::JsonResponseReadyMessage> + (*this, &RadiographyScene::OnDicomExported), + NULL, NULL); + } + // Export using PAM is faster than using PNG, but requires Orthanc + // core >= 1.4.3 + void RadiographyScene::ExportDicom(OrthancApiClient& orthanc, + const Orthanc::DicomMap& dicom, + const std::string& parentOrthancId, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation, + bool usePam) + { + std::set<Orthanc::DicomTag> tags; + dicom.GetTags(tags); + + Json::Value jsonTags = Json::objectValue; + + for (std::set<Orthanc::DicomTag>::const_iterator + tag = tags.begin(); tag != tags.end(); ++tag) + { + const Orthanc::DicomValue& value = dicom.GetValue(*tag); + if (!value.IsNull() && + !value.IsBinary()) + { + jsonTags[tag->Format()] = value.GetContent(); + } + } + + ExportDicom(orthanc, jsonTags, parentOrthancId, pixelSpacingX, pixelSpacingY, invert, interpolation, usePam); + } + void RadiographyScene::OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) { LOG(INFO) << "DICOM export was successful: "
--- a/Framework/Radiography/RadiographyScene.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.h Thu Apr 18 09:30:00 2019 +0200 @@ -24,16 +24,21 @@ #include "RadiographyLayer.h" #include "../Toolbox/OrthancApiClient.h" #include "Framework/StoneEnumerations.h" +#include "Core/Images/Image.h" +#include "Core/Images/ImageProcessing.h" namespace OrthancStone { + class RadiographyDicomLayer; + class DicomFrameConverter; + class RadiographyScene : public IObserver, public IObservable { public: class GeometryChangedMessage : - public OriginMessage<MessageType_Scene_GeometryChanged, RadiographyScene> + public OriginMessage<MessageType_RadiographyScene_GeometryChanged, RadiographyScene> { private: RadiographyLayer& layer_; @@ -53,7 +58,7 @@ }; class ContentChangedMessage : - public OriginMessage<MessageType_Scene_ContentChanged, RadiographyScene> + public OriginMessage<MessageType_RadiographyScene_ContentChanged, RadiographyScene> { private: RadiographyLayer& layer_; @@ -72,6 +77,37 @@ } }; + class LayerEditedMessage : + public OriginMessage<MessageType_RadiographyScene_LayerEdited, RadiographyScene> + { + private: + const RadiographyLayer& layer_; + + public: + LayerEditedMessage(const RadiographyScene& origin, + const RadiographyLayer& layer) : + OriginMessage(origin), + layer_(layer) + { + } + + const RadiographyLayer& GetLayer() const + { + return layer_; + } + + }; + + class WindowingChangedMessage : + public OriginMessage<MessageType_RadiographyScene_WindowingChanged, RadiographyScene> + { + + public: + WindowingChangedMessage(const RadiographyScene& origin) : + OriginMessage(origin) + { + } + }; class LayerAccessor : public boost::noncopyable { @@ -126,6 +162,7 @@ void OnDicomWebReceived(const IWebService::HttpRequestSuccessMessage& message); + void OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message); public: RadiographyScene(MessageBroker& broker); @@ -150,9 +187,21 @@ unsigned int height, RadiographyLayer::Geometry* geometry); + RadiographyLayer& LoadMask(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners, + const RadiographyDicomLayer& dicomLayer, + float foreground, + RadiographyLayer::Geometry* geometry); + RadiographyLayer& LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, // takes ownership RadiographyLayer::Geometry* geometry); + virtual RadiographyLayer& LoadDicomImage(Orthanc::ImageAccessor* dicomImage, // takes ownership + const std::string& instance, + unsigned int frame, + DicomFrameConverter* converter, // takes ownership + PhotometricDisplayMode preferredPhotometricDisplayMode, + RadiographyLayer::Geometry* geometry); + virtual RadiographyLayer& LoadDicomFrame(OrthancApiClient& orthanc, const std::string& instance, unsigned int frame, @@ -165,6 +214,54 @@ const RadiographyLayer& GetLayer(size_t layerIndex) const; + template <typename TypeLayer> + TypeLayer* GetLayer(size_t index = 0) + { + std::vector<size_t> layerIndexes; + GetLayersIndexes(layerIndexes); + + size_t count = 0; + + for (size_t i = 0; i < layerIndexes.size(); ++i) + { + TypeLayer* typedLayer = dynamic_cast<TypeLayer*>(layers_[layerIndexes[i]]); + if (typedLayer != NULL) + { + if (count == index) + { + return typedLayer; + } + count++; + } + } + + return NULL; + } + + template <typename TypeLayer> + const TypeLayer* GetLayer(size_t index = 0) const + { + std::vector<size_t> layerIndexes; + GetLayersIndexes(layerIndexes); + + size_t count = 0; + + for (size_t i = 0; i < layerIndexes.size(); ++i) + { + const TypeLayer* typedLayer = dynamic_cast<const TypeLayer*>(layers_.at(layerIndexes[i])); + if (typedLayer != NULL) + { + if (count == index) + { + return typedLayer; + } + count++; + } + } + + return NULL; + } + void GetLayersIndexes(std::vector<size_t>& output) const; Extent2D GetSceneExtent() const; @@ -195,13 +292,35 @@ ImageInterpolation interpolation, bool usePam); - // temporary version used by VSOL because we need to send the same request at another url + void ExportDicom(OrthancApiClient& orthanc, + const Json::Value& dicomTags, + const std::string& parentOrthancId, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation, + bool usePam); + void ExportToCreateDicomRequest(Json::Value& createDicomRequestContent, - const Orthanc::DicomMap& dicom, + const Json::Value& dicomTags, + const std::string& parentOrthancId, double pixelSpacingX, double pixelSpacingY, bool invert, ImageInterpolation interpolation, bool usePam); + + Orthanc::Image* ExportToImage(double pixelSpacingX, + double pixelSpacingY, + ImageInterpolation interpolation) + { + return ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false, 0); + } + + Orthanc::Image* ExportToImage(double pixelSpacingX, + double pixelSpacingY, + ImageInterpolation interpolation, + bool invert, + int64_t maxValue /* for inversion */); }; }
--- a/Framework/Radiography/RadiographySceneReader.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographySceneReader.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -28,13 +28,37 @@ namespace OrthancStone { - void RadiographySceneReader::Read(const Json::Value& input) + + void RadiographySceneBuilder::Read(const Json::Value& input, Orthanc::ImageAccessor* dicomImage /* takes ownership */, + DicomFrameConverter* dicomFrameConverter /* takes ownership */, + PhotometricDisplayMode preferredPhotometricDisplayMode + ) + { + dicomImage_.reset(dicomImage); + dicomFrameConverter_.reset(dicomFrameConverter); + preferredPhotometricDisplayMode_ = preferredPhotometricDisplayMode; + Read(input); + } + + RadiographyDicomLayer* RadiographySceneBuilder::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry) + { + return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomImage(dicomImage_.release(), instanceId, frame, dicomFrameConverter_.release(), preferredPhotometricDisplayMode_, geometry))); + } + + + RadiographyDicomLayer* RadiographySceneReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry) + { + return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, instanceId, frame, false, geometry))); + } + + void RadiographySceneBuilder::Read(const Json::Value& input) { unsigned int version = input["version"].asUInt(); if (version != 1) throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + RadiographyDicomLayer* dicomLayer = NULL; for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++) { const Json::Value& jsonLayer = input["layers"][(int)layerIndex]; @@ -43,7 +67,26 @@ if (jsonLayer["type"].asString() == "dicom") { ReadLayerGeometry(geometry, jsonLayer); - scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry); + dicomLayer = LoadDicom(jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), &geometry); + } + else if (jsonLayer["type"].asString() == "mask") + { + if (dicomLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // we always assumed the dicom layer was read before the mask + } + ReadLayerGeometry(geometry, jsonLayer); + + float foreground = jsonLayer["foreground"].asFloat(); + std::vector<Orthanc::ImageProcessing::ImagePoint> corners; + for (size_t i = 0; i < jsonLayer["corners"].size(); i++) + { + Orthanc::ImageProcessing::ImagePoint corner(jsonLayer["corners"][(int)i]["x"].asInt(), + jsonLayer["corners"][(int)i]["y"].asInt()); + corners.push_back(corner); + } + + scene_.LoadMask(corners, *dicomLayer, foreground, &geometry); } else if (jsonLayer["type"].asString() == "text") { @@ -90,7 +133,90 @@ } } - void RadiographySceneReader::ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& jsonLayer) + + void RadiographySceneReader::Read(const Json::Value& input) + { + unsigned int version = input["version"].asUInt(); + + if (version != 1) + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + + RadiographyDicomLayer* dicomLayer = NULL; + for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++) + { + const Json::Value& jsonLayer = input["layers"][(int)layerIndex]; + RadiographyLayer::Geometry geometry; + + if (jsonLayer["type"].asString() == "dicom") + { + ReadLayerGeometry(geometry, jsonLayer); + dicomLayer = dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry))); + } + else if (jsonLayer["type"].asString() == "mask") + { + if (dicomLayer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // we always assumed the dicom layer was read before the mask + } + ReadLayerGeometry(geometry, jsonLayer); + + float foreground = jsonLayer["foreground"].asFloat(); + std::vector<Orthanc::ImageProcessing::ImagePoint> corners; + for (size_t i = 0; i < jsonLayer["corners"].size(); i++) + { + Orthanc::ImageProcessing::ImagePoint corner(jsonLayer["corners"][(int)i]["x"].asInt(), + jsonLayer["corners"][(int)i]["y"].asInt()); + corners.push_back(corner); + } + + scene_.LoadMask(corners, *dicomLayer, foreground, &geometry); + } + else if (jsonLayer["type"].asString() == "text") + { + if (fontRegistry_ == NULL || fontRegistry_->GetSize() == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); // you must provide a FontRegistry if you need to re-create text layers. + } + + ReadLayerGeometry(geometry, jsonLayer); + const Orthanc::Font* font = fontRegistry_->FindFont(jsonLayer["fontName"].asString()); + if (font == NULL) // if not found, take the first font in the registry + { + font = &(fontRegistry_->GetFont(0)); + } + scene_.LoadText(*font, jsonLayer["text"].asString(), &geometry); + } + else if (jsonLayer["type"].asString() == "alpha") + { + ReadLayerGeometry(geometry, jsonLayer); + + const std::string& pngContentBase64 = jsonLayer["content"].asString(); + std::string pngContent; + std::string mimeType; + Orthanc::Toolbox::DecodeDataUriScheme(mimeType, pngContent, pngContentBase64); + + std::auto_ptr<Orthanc::ImageAccessor> image; + if (mimeType == "image/png") + { + image.reset(new Orthanc::PngReader()); + dynamic_cast<Orthanc::PngReader*>(image.get())->ReadFromMemory(pngContent); + } + else + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + + RadiographyAlphaLayer& layer = dynamic_cast<RadiographyAlphaLayer&>(scene_.LoadAlphaBitmap(image.release(), &geometry)); + + if (!jsonLayer["isUsingWindowing"].asBool()) + { + layer.SetForegroundValue((float)(jsonLayer["foreground"].asDouble())); + } + } + else + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + void RadiographySceneBuilder::ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& jsonLayer) { {// crop unsigned int x, y, width, height;
--- a/Framework/Radiography/RadiographySceneReader.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographySceneReader.h Thu Apr 18 09:30:00 2019 +0200 @@ -24,6 +24,7 @@ #include "RadiographyScene.h" #include "RadiographyAlphaLayer.h" #include "RadiographyDicomLayer.h" +#include "RadiographyMaskLayer.h" #include "RadiographyTextLayer.h" #include <json/value.h> #include <Core/Images/FontRegistry.h> @@ -32,28 +33,57 @@ { class OrthancApiClient; - class RadiographySceneReader : public boost::noncopyable + // HACK: I had to introduce this builder class in order to be able to recreate a RadiographyScene + // from a serialized scene that is passed to web-workers. + // It needs some architecturing... + class RadiographySceneBuilder : public boost::noncopyable { - RadiographyScene& scene_; - OrthancApiClient& orthancApiClient_; - const Orthanc::FontRegistry* fontRegistry_; + protected: + RadiographyScene& scene_; + const Orthanc::FontRegistry* fontRegistry_; + std::auto_ptr<Orthanc::ImageAccessor> dicomImage_; + std::auto_ptr<DicomFrameConverter> dicomFrameConverter_; + PhotometricDisplayMode preferredPhotometricDisplayMode_; public: - RadiographySceneReader(RadiographyScene& scene, OrthancApiClient& orthancApiClient) : + RadiographySceneBuilder(RadiographyScene& scene) : scene_(scene), - orthancApiClient_(orthancApiClient), fontRegistry_(NULL) { } - void Read(const Json::Value& output); + void Read(const Json::Value& input); + void Read(const Json::Value& input, + Orthanc::ImageAccessor* dicomImage, // takes ownership + DicomFrameConverter* dicomFrameConverter, // takes ownership + PhotometricDisplayMode preferredPhotometricDisplayMode + ); void SetFontRegistry(const Orthanc::FontRegistry& fontRegistry) { fontRegistry_ = &fontRegistry; } - private: - void ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& output); + protected: + void ReadLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& input); + virtual RadiographyDicomLayer* LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry); + }; + + + class RadiographySceneReader : public RadiographySceneBuilder + { + OrthancApiClient& orthancApiClient_; + + public: + RadiographySceneReader(RadiographyScene& scene, OrthancApiClient& orthancApiClient) : + RadiographySceneBuilder(scene), + orthancApiClient_(orthancApiClient) + { + } + + void Read(const Json::Value& input); + + protected: + virtual RadiographyDicomLayer* LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry); }; }
--- a/Framework/Radiography/RadiographySceneWriter.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographySceneWriter.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -57,6 +57,22 @@ output["fontName"] = layer.GetFontName(); } + void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyMaskLayer& layer) + { + output["type"] = "mask"; + output["instanceId"] = layer.GetInstanceId(); // the dicom layer it's being linked to + output["foreground"] = layer.GetForeground(); + output["corners"] = Json::arrayValue; + const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners = layer.GetCorners(); + for (size_t i = 0; i < corners.size(); i++) + { + Json::Value corner; + corner["x"] = corners[i].GetX(); + corner["y"] = corners[i].GetY(); + output["corners"].append(corner); + } + } + void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer) { output["type"] = "alpha"; @@ -128,6 +144,10 @@ { WriteLayer(output, dynamic_cast<const RadiographyAlphaLayer&>(layer)); } + else if (dynamic_cast<const RadiographyMaskLayer*>(&layer) != NULL) + { + WriteLayer(output, dynamic_cast<const RadiographyMaskLayer&>(layer)); + } else { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
--- a/Framework/Radiography/RadiographySceneWriter.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographySceneWriter.h Thu Apr 18 09:30:00 2019 +0200 @@ -25,6 +25,7 @@ #include "RadiographyAlphaLayer.h" #include "RadiographyDicomLayer.h" #include "RadiographyTextLayer.h" +#include "RadiographyMaskLayer.h" #include <json/value.h> namespace OrthancStone @@ -46,5 +47,6 @@ void WriteLayer(Json::Value& output, const RadiographyDicomLayer& layer); void WriteLayer(Json::Value& output, const RadiographyTextLayer& layer); void WriteLayer(Json::Value& output, const RadiographyAlphaLayer& layer); + void WriteLayer(Json::Value& output, const RadiographyMaskLayer& layer); }; }
--- a/Framework/Radiography/RadiographyTextLayer.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyTextLayer.h Thu Apr 18 09:30:00 2019 +0200 @@ -34,8 +34,8 @@ std::string fontName_; public: - RadiographyTextLayer(const RadiographyScene& scene) : - RadiographyAlphaLayer(scene) + RadiographyTextLayer(MessageBroker& broker, const RadiographyScene& scene) : + RadiographyAlphaLayer(broker, scene) { }
--- a/Framework/Radiography/RadiographyWidget.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyWidget.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -25,16 +25,21 @@ #include <Core/Images/Image.h> #include <Core/Images/ImageProcessing.h> +#include "RadiographyMaskLayer.h" namespace OrthancStone { bool RadiographyWidget::IsInvertedInternal() const { - return (scene_->GetPreferredPhotomotricDisplayMode() == PhotometricDisplayMode_Monochrome1) ^ invert_; // MONOCHROME1 images must be inverted and the user can invert the image too -> XOR the two + // MONOCHROME1 images must be inverted and the user can invert the + // image, too -> XOR the two + return (scene_->GetPreferredPhotomotricDisplayMode() == + PhotometricDisplayMode_Monochrome1) ^ invert_; } - void RadiographyWidget::RenderBackground(Orthanc::ImageAccessor& image, float minValue, float maxValue) + void RadiographyWidget::RenderBackground( + Orthanc::ImageAccessor& image, float minValue, float maxValue) { // wipe background before rendering float backgroundValue = minValue; @@ -58,7 +63,7 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - Orthanc::ImageProcessing::Set(image, backgroundValue); + Orthanc::ImageProcessing::Set(image, static_cast<int64_t>(backgroundValue)); } bool RadiographyWidget::RenderInternal(unsigned int width, @@ -81,7 +86,8 @@ floatBuffer_->GetWidth() != width || floatBuffer_->GetHeight() != height) { - floatBuffer_.reset(new Orthanc::Image(Orthanc::PixelFormat_Float32, width, height, false)); + floatBuffer_.reset(new Orthanc::Image( + Orthanc::PixelFormat_Float32, width, height, false)); } if (cairoBuffer_.get() == NULL || @@ -167,7 +173,8 @@ if (hasSelection_) { - scene_->DrawBorder(context, selectedLayer_, view.GetZoom()); + scene_->DrawBorder( + context, static_cast<unsigned int>(selectedLayer_), view.GetZoom()); } return true; @@ -194,6 +201,28 @@ selectedLayer_ = layer; } + bool RadiographyWidget::SelectMaskLayer(size_t index) + { + std::vector<size_t> layerIndexes; + size_t count = 0; + scene_->GetLayersIndexes(layerIndexes); + + for (size_t i = 0; i < layerIndexes.size(); ++i) + { + const RadiographyMaskLayer* maskLayer = dynamic_cast<const RadiographyMaskLayer*>(&(scene_->GetLayer(layerIndexes[i]))); + if (maskLayer != NULL) + { + if (count == index) + { + Select(layerIndexes[i]); + return true; + } + count++; + } + } + + return false; + } bool RadiographyWidget::LookupSelectedLayer(size_t& layer) {
--- a/Framework/Radiography/RadiographyWidget.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyWidget.h Thu Apr 18 09:30:00 2019 +0200 @@ -27,6 +27,8 @@ namespace OrthancStone { + class RadiographyMaskLayer; + class RadiographyWidget : public WorldSceneWidget, public IObserver @@ -76,6 +78,8 @@ void Select(size_t layer); + bool SelectMaskLayer(size_t index = 0); + bool LookupSelectedLayer(size_t& layer); void OnGeometryChanged(const RadiographyScene::GeometryChangedMessage& message);
--- a/Framework/Radiography/RadiographyWindowingTracker.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyWindowingTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -163,7 +163,9 @@ void RadiographyWindowingTracker::MouseMove(int displayX, int displayY, double sceneX, - double sceneY) + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) { // This follows the behavior of the Osimis Web viewer: // https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/app/viewport/image-plugins/windowing-viewport-tool.class.js
--- a/Framework/Radiography/RadiographyWindowingTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Radiography/RadiographyWindowingTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -82,6 +82,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY); + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); }; }
--- a/Framework/StoneEnumerations.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/StoneEnumerations.h Thu Apr 18 09:30:00 2019 +0200 @@ -157,8 +157,12 @@ MessageType_OrthancApi_GenericHttpError_Ready, MessageType_OrthancApi_GenericEmptyResponse_Ready, - MessageType_Scene_GeometryChanged, - MessageType_Scene_ContentChanged, + MessageType_RadiographyScene_GeometryChanged, + MessageType_RadiographyScene_ContentChanged, + MessageType_RadiographyScene_LayerEdited, + MessageType_RadiographyScene_WindowingChanged, + + MessageType_RadiographyLayer_Edited, MessageType_ViewportChanged, @@ -172,12 +176,12 @@ }; - enum Corner + enum ControlPointType { - Corner_TopLeft, - Corner_TopRight, - Corner_BottomLeft, - Corner_BottomRight + ControlPoint_TopLeftCorner = 0, + ControlPoint_TopRightCorner = 1, + ControlPoint_BottomRightCorner = 2, + ControlPoint_BottomLeftCorner = 3 }; enum PhotometricDisplayMode
--- a/Framework/Toolbox/DicomFrameConverter.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Toolbox/DicomFrameConverter.h Thu Apr 18 09:30:00 2019 +0200 @@ -59,6 +59,17 @@ SetDefaultParameters(); } + ~DicomFrameConverter() + { + // TODO: check whether this dtor is called or not + // An MSVC warning explains that declaring an + // std::auto_ptr with a forward-declared type + // prevents its dtor from being called. Does not + // seem an issue here (only POD types inside), but + // definitely something to keep an eye on. + (void)0; + } + Orthanc::PixelFormat GetExpectedPixelFormat() const { return expectedPixelFormat_;
--- a/Framework/Toolbox/DicomStructureSet.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Toolbox/DicomStructureSet.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -754,7 +754,11 @@ double x1, y1, x2, y2; if (polygon->Project(x1, y1, x2, y2, slice)) { - projected.push_back(CreateRectangle(x1, y1, x2, y2)); + projected.push_back(CreateRectangle( + static_cast<float>(x1), + static_cast<float>(y1), + static_cast<float>(x2), + static_cast<float>(y2))); } }
--- a/Framework/Toolbox/FiniteProjectiveCamera.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Toolbox/FiniteProjectiveCamera.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -332,7 +332,7 @@ LOG(INFO) << "Applying raytracer on slice: " << z << "/" << slices->GetSliceCount(); const OrthancStone::CoordinateSystem3D& slice = slices->GetSlice(z); - OrthancStone::ImageBuffer3D::SliceReader sliceReader(source, projection, z); + OrthancStone::ImageBuffer3D::SliceReader sliceReader(source, projection, static_cast<unsigned int>(z)); SourceReader pixelReader(sliceReader.GetAccessor());
--- a/Framework/Toolbox/ImageGeometry.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Toolbox/ImageGeometry.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -76,9 +76,7 @@ } else { - int tmp; - - tmp = std::floor(extent.GetX1()); + int tmp = static_cast<int>(std::floor(extent.GetX1())); if (tmp < 0) { x1 = 0; @@ -88,7 +86,7 @@ x1 = static_cast<unsigned int>(tmp); } - tmp = std::floor(extent.GetY1()); + tmp = static_cast<int>(std::floor(extent.GetY1())); if (tmp < 0) { y1 = 0; @@ -98,7 +96,7 @@ y1 = static_cast<unsigned int>(tmp); } - tmp = std::ceil(extent.GetX2()); + tmp = static_cast<int>(std::ceil(extent.GetX2())); if (tmp < 0) { return false; @@ -112,7 +110,7 @@ x2 = static_cast<unsigned int>(tmp); } - tmp = std::ceil(extent.GetY2()); + tmp = static_cast<int>(std::ceil(extent.GetY2())); if (tmp < 0) { return false; @@ -395,8 +393,8 @@ Reader reader(source); unsigned int x1, y1, x2, y2; - const float floatWidth = source.GetWidth(); - const float floatHeight = source.GetHeight(); + const float floatWidth = static_cast<float>(source.GetWidth()); + const float floatHeight = static_cast<float>(source.GetHeight()); if (GetProjectiveTransformExtent(x1, y1, x2, y2, a, source.GetWidth(), source.GetHeight(),
--- a/Framework/Toolbox/OrthancApiClient.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Toolbox/OrthancApiClient.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -248,6 +248,21 @@ web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, NULL, NULL, NULL); } + void OrthancApiClient::PostBinaryAsync( + const std::string& uri, + const std::string& body, + MessageHandler<EmptyResponseReadyMessage>* successCallback, + MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, + Orthanc::IDynamicObject* payload /* takes ownership */) + { + web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, + new WebServicePayload(successCallback, failureCallback, payload), + new Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> + (*this, &OrthancApiClient::NotifyHttpSuccess), + new Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> + (*this, &OrthancApiClient::NotifyHttpError)); + } + void OrthancApiClient::PostJsonAsyncExpectJson( const std::string& uri, const Json::Value& data, @@ -269,6 +284,18 @@ return PostBinaryAsync(uri, body); } + void OrthancApiClient::PostJsonAsync( + const std::string& uri, + const Json::Value& data, + MessageHandler<EmptyResponseReadyMessage>* successCallback, + MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, + Orthanc::IDynamicObject* payload /* takes ownership */) + { + std::string body; + MessagingToolbox::JsonToString(body, data); + return PostBinaryAsync(uri, body, successCallback, failureCallback, payload); + } + void OrthancApiClient::DeleteAsync( const std::string& uri, MessageHandler<EmptyResponseReadyMessage>* successCallback,
--- a/Framework/Toolbox/OrthancApiClient.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Toolbox/OrthancApiClient.h Thu Apr 18 09:30:00 2019 +0200 @@ -163,6 +163,8 @@ { } + const std::string& GetBaseUrl() const {return baseUrl_;} + // schedule a GET request expecting a JSON response. void GetJsonAsync(const std::string& uri, MessageHandler<JsonResponseReadyMessage>* successCallback, @@ -201,10 +203,25 @@ void PostJsonAsync(const std::string& uri, const Json::Value& data); + // schedule a POST request and don't expect any response. + void PostJsonAsync(const std::string& uri, + const Json::Value& data, + MessageHandler<EmptyResponseReadyMessage>* successCallback, + MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL /* takes ownership */); + + // schedule a POST request and don't mind the response. void PostBinaryAsync(const std::string& uri, const std::string& body); + // schedule a POST request and don't expect any response. + void PostBinaryAsync(const std::string& uri, + const std::string& body, + MessageHandler<EmptyResponseReadyMessage>* successCallback, + MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL /* takes ownership */); + // schedule a DELETE request expecting an empty response. void DeleteAsync(const std::string& uri, MessageHandler<EmptyResponseReadyMessage>* successCallback,
--- a/Framework/Toolbox/ViewportGeometry.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Toolbox/ViewportGeometry.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -121,6 +121,18 @@ } + void ViewportGeometry::MapPixelCenterToScene(std::vector<Touch>& sceneTouches /* out */, + const std::vector<Touch>& displayTouches) const + { + double sceneX, sceneY; + sceneTouches.clear(); + for (size_t t = 0; t < displayTouches.size(); t++) + { + MapPixelCenterToScene(sceneX, sceneY, displayTouches[t].x, displayTouches[t].y); + sceneTouches.push_back(Touch((float)sceneX, (float)sceneY)); + } + } + void ViewportGeometry::MapPixelCenterToScene(double& sceneX, double& sceneY, int x,
--- a/Framework/Toolbox/ViewportGeometry.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Toolbox/ViewportGeometry.h Thu Apr 18 09:30:00 2019 +0200 @@ -24,6 +24,7 @@ #include "../Viewport/CairoContext.h" #include "Extent2D.h" #include "LinearAlgebra.h" +#include "../Viewport/IMouseTracker.h" // to include "Touch" definition namespace OrthancStone { @@ -69,6 +70,9 @@ int x, int y) const; + void MapPixelCenterToScene(std::vector<Touch>& sceneTouches /* out */, + const std::vector<Touch>& displayTouches) const; + void MapSceneToDisplay(int& displayX /* out */, int& displayY /* out */, double x,
--- a/Framework/Viewport/IMouseTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Viewport/IMouseTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -22,9 +22,28 @@ #pragma once #include "CairoSurface.h" +#include <vector> namespace OrthancStone { + struct Touch + { + float x; + float y; + + Touch(float x, float y) + : x(x), + y(y) + { + } + Touch() + : x(0.0f), + y(0.0f) + { + } + }; + + // this is tracking a mouse in screen coordinates/pixels unlike // the IWorldSceneMouseTracker that is tracking a mouse // in scene coordinates/mm. @@ -41,6 +60,9 @@ // Returns "true" iff. the background scene must be repainted virtual void MouseMove(int x, - int y) = 0; + int y, + const std::vector<Touch>& displayTouches) = 0; + + virtual bool IsTouchTracker() const {return false;} }; }
--- a/Framework/Viewport/IViewport.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Viewport/IViewport.h Thu Apr 18 09:30:00 2019 +0200 @@ -26,6 +26,7 @@ #include "../Messages/IObservable.h" #include <Core/Images/ImageAccessor.h> +#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition namespace OrthancStone { @@ -58,12 +59,14 @@ virtual void MouseDown(MouseButton button, int x, int y, - KeyboardModifiers modifiers) = 0; + KeyboardModifiers modifiers, + const std::vector<Touch>& touches) = 0; virtual void MouseUp() = 0; virtual void MouseMove(int x, - int y) = 0; + int y, + const std::vector<Touch>& displayTouches) = 0; virtual void MouseEnter() = 0;
--- a/Framework/Viewport/WidgetViewport.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Viewport/WidgetViewport.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -137,18 +137,36 @@ return true; } + void WidgetViewport::TouchStart(const std::vector<Touch>& displayTouches) + { + MouseDown(MouseButton_Left, (int)displayTouches[0].x, (int)displayTouches[0].y, KeyboardModifiers_None, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates + } + + void WidgetViewport::TouchMove(const std::vector<Touch>& displayTouches) + { + MouseMove((int)displayTouches[0].x, (int)displayTouches[0].y, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates + } + + void WidgetViewport::TouchEnd(const std::vector<Touch>& displayTouches) + { + // note: TouchEnd is not triggered when a single touch gesture ends (it is only triggered when + // going from 2 touches to 1 touch, ...) + MouseUp(); + } void WidgetViewport::MouseDown(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector<Touch>& displayTouches + ) { lastMouseX_ = x; lastMouseY_ = y; if (centralWidget_.get() != NULL) { - mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers)); + mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers, displayTouches)); } else { @@ -171,7 +189,8 @@ void WidgetViewport::MouseMove(int x, - int y) + int y, + const std::vector<Touch>& displayTouches) { if (centralWidget_.get() == NULL) { @@ -185,7 +204,7 @@ if (mouseTracker_.get() != NULL) { - mouseTracker_->MouseMove(x, y); + mouseTracker_->MouseMove(x, y, displayTouches); repaint = true; } else
--- a/Framework/Viewport/WidgetViewport.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Viewport/WidgetViewport.h Thu Apr 18 09:30:00 2019 +0200 @@ -59,17 +59,25 @@ virtual void MouseDown(MouseButton button, int x, int y, - KeyboardModifiers modifiers); + KeyboardModifiers modifiers, + const std::vector<Touch>& displayTouches); virtual void MouseUp(); virtual void MouseMove(int x, - int y); + int y, + const std::vector<Touch>& displayTouches); virtual void MouseEnter(); virtual void MouseLeave(); + virtual void TouchStart(const std::vector<Touch>& touches); + + virtual void TouchMove(const std::vector<Touch>& touches); + + virtual void TouchEnd(const std::vector<Touch>& touches); + virtual void MouseWheel(MouseWheelDirection direction, int x, int y,
--- a/Framework/Widgets/EmptyWidget.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/EmptyWidget.h Thu Apr 18 09:30:00 2019 +0200 @@ -76,7 +76,8 @@ virtual IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector<Touch>& touches) { return NULL; }
--- a/Framework/Widgets/IWidget.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/IWidget.h Thu Apr 18 09:30:00 2019 +0200 @@ -52,7 +52,8 @@ virtual IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) = 0; + KeyboardModifiers modifiers, + const std::vector<Touch>& touches) = 0; virtual void RenderMouseOver(Orthanc::ImageAccessor& target, int x,
--- a/Framework/Widgets/IWorldSceneInteractor.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/IWorldSceneInteractor.h Thu Apr 18 09:30:00 2019 +0200 @@ -46,7 +46,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) = 0; + IStatusBar* statusBar, + const std::vector<Touch>& touches) = 0; virtual void MouseOver(CairoContext& context, WorldSceneWidget& widget,
--- a/Framework/Widgets/IWorldSceneMouseTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/IWorldSceneMouseTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -22,6 +22,7 @@ #pragma once #include "../Viewport/CairoContext.h" +#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition namespace OrthancStone { @@ -46,6 +47,8 @@ virtual void MouseMove(int displayX, int displayY, double sceneX, - double sceneY) = 0; + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) = 0; }; }
--- a/Framework/Widgets/LayoutWidget.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/LayoutWidget.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -68,9 +68,16 @@ } virtual void MouseMove(int x, - int y) + int y, + const std::vector<Touch>& displayTouches) { - tracker_->MouseMove(x - left_, y - top_); + std::vector<Touch> relativeTouches; + for (size_t t = 0; t < displayTouches.size(); t++) + { + relativeTouches.push_back(Touch(displayTouches[t].x - left_, displayTouches[t].y - top_)); + } + + tracker_->MouseMove(x - left_, y - top_, relativeTouches); } }; @@ -150,14 +157,16 @@ IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector<Touch>& touches) { if (Contains(x, y)) { IMouseTracker* tracker = widget_->CreateMouseTracker(button, x - left_, y - top_, - modifiers); + modifiers, + touches); if (tracker) { return new LayoutMouseTracker(tracker, left_, top_, width_, height_); @@ -217,7 +226,7 @@ } else if (isHorizontal_) { - unsigned int padding = paddingLeft_ + paddingRight_ + (children_.size() - 1) * paddingInternal_; + unsigned int padding = paddingLeft_ + paddingRight_ + (static_cast<unsigned int>(children_.size()) - 1) * paddingInternal_; float childWidth = ((static_cast<float>(width_) - static_cast<float>(padding)) / static_cast<float>(children_.size())); @@ -241,7 +250,7 @@ } else { - unsigned int padding = paddingTop_ + paddingBottom_ + (children_.size() - 1) * paddingInternal_; + unsigned int padding = paddingTop_ + paddingBottom_ + (static_cast<unsigned int>(children_.size()) - 1) * paddingInternal_; float childHeight = ((static_cast<float>(height_) - static_cast<float>(padding)) / static_cast<float>(children_.size())); @@ -413,11 +422,12 @@ IMouseTracker* LayoutWidget::CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector<Touch>& touches) { for (size_t i = 0; i < children_.size(); i++) { - IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers); + IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers, touches); if (tracker != NULL) { return tracker;
--- a/Framework/Widgets/LayoutWidget.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/LayoutWidget.h Thu Apr 18 09:30:00 2019 +0200 @@ -106,7 +106,8 @@ virtual IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers); + KeyboardModifiers modifiers, + const std::vector<Touch>& touches); virtual void RenderMouseOver(Orthanc::ImageAccessor& target, int x,
--- a/Framework/Widgets/PanMouseTracker.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/PanMouseTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -46,7 +46,9 @@ void PanMouseTracker::MouseMove(int displayX, int displayY, double x, - double y) + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) { ViewportGeometry view = that_.GetView(); view.SetPan(originalPanX_ + (x - downX_) * view.GetZoom(),
--- a/Framework/Widgets/PanMouseTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/PanMouseTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -54,6 +54,8 @@ virtual void MouseMove(int displayX, int displayY, double x, - double y); + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Widgets/PanZoomMouseTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,137 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "PanZoomMouseTracker.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> +#include <math.h> + +namespace OrthancStone +{ + Touch GetCenter(const std::vector<Touch>& touches) + { + return Touch((touches[0].x + touches[1].x) / 2.0f, (touches[0].y + touches[1].y) / 2.0f); + } + + double GetDistance(const std::vector<Touch>& touches) + { + float dx = touches[0].x - touches[1].x; + float dy = touches[0].y - touches[1].y; + return sqrt((double)(dx * dx) + (double)(dy * dy)); + } + + + PanZoomMouseTracker::PanZoomMouseTracker(WorldSceneWidget& that, + const std::vector<Touch>& startTouches) + : that_(that), + originalZoom_(that.GetView().GetZoom()) + { + that.GetView().GetPan(originalPanX_, originalPanY_); + that.GetView().MapPixelCenterToScene(originalSceneTouches_, startTouches); + + originalDisplayCenter_ = GetCenter(startTouches); + originalSceneCenter_ = GetCenter(originalSceneTouches_); + originalDisplayDistanceBetweenTouches_ = GetDistance(startTouches); + +// printf("original Pan %f %f\n", originalPanX_, originalPanY_); +// printf("original Zoom %f \n", originalZoom_); +// printf("original distance %f \n", (float)originalDisplayDistanceBetweenTouches_); +// printf("original display touches 0 %f %f\n", startTouches[0].x, startTouches[0].y); +// printf("original display touches 1 %f %f\n", startTouches[1].x, startTouches[1].y); +// printf("original Scene center %f %f\n", originalSceneCenter_.x, originalSceneCenter_.y); + + unsigned int height = that.GetView().GetDisplayHeight(); + + if (height <= 3) + { + idle_ = true; + LOG(WARNING) << "image is too small to zoom (current height = " << height << ")"; + } + else + { + idle_ = false; + normalization_ = 1.0 / static_cast<double>(height - 1); + } + + } + + + void PanZoomMouseTracker::Render(CairoContext& context, + double zoom) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + + void PanZoomMouseTracker::MouseMove(int displayX, + int displayY, + double x, + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) + { + ViewportGeometry view = that_.GetView(); + +// printf("Display touches 0 %f %f\n", displayTouches[0].x, displayTouches[0].y); +// printf("Display touches 1 %f %f\n", displayTouches[1].x, displayTouches[1].y); +// printf("Scene touches 0 %f %f\n", sceneTouches[0].x, sceneTouches[0].y); +// printf("Scene touches 1 %f %f\n", sceneTouches[1].x, sceneTouches[1].y); + +// printf("zoom = %f\n", view.GetZoom()); + Touch currentSceneCenter = GetCenter(sceneTouches); + double panX = originalPanX_ + (currentSceneCenter.x - originalSceneCenter_.x) * view.GetZoom(); + double panY = originalPanY_ + (currentSceneCenter.y - originalSceneCenter_.y) * view.GetZoom(); + + view.SetPan(panX, panY); + + static const double MIN_ZOOM = -4; + static const double MAX_ZOOM = 4; + + if (!idle_) + { + double currentDistanceBetweenTouches = GetDistance(displayTouches); + + double dy = static_cast<double>(currentDistanceBetweenTouches - originalDisplayDistanceBetweenTouches_) * normalization_; // In the range [-1,1] + double z; + + // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM] + if (dy < -1.0) + { + z = MIN_ZOOM; + } + else if (dy > 1.0) + { + z = MAX_ZOOM; + } + else + { + z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0; + } + + z = pow(2.0, z); + + view.SetZoom(z * originalZoom_); + } + + that_.SetView(view); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Widgets/PanZoomMouseTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,65 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "WorldSceneWidget.h" + +namespace OrthancStone +{ + class PanZoomMouseTracker : public IWorldSceneMouseTracker + { + private: + WorldSceneWidget& that_; + std::vector<Touch> originalSceneTouches_; + Touch originalSceneCenter_; + Touch originalDisplayCenter_; + double originalPanX_; + double originalPanY_; + double originalZoom_; + double originalDisplayDistanceBetweenTouches_; + bool idle_; + double normalization_; + + public: + PanZoomMouseTracker(WorldSceneWidget& that, + const std::vector<Touch>& startTouches); + + virtual bool HasRender() const + { + return false; + } + + virtual void MouseUp() + { + } + + virtual void Render(CairoContext& context, + double zoom); + + virtual void MouseMove(int displayX, + int displayY, + double x, + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); + }; +}
--- a/Framework/Widgets/SliceViewerWidget.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/SliceViewerWidget.h Thu Apr 18 09:30:00 2019 +0200 @@ -60,6 +60,9 @@ }; private: + SliceViewerWidget(const SliceViewerWidget&); + SliceViewerWidget& operator=(const SliceViewerWidget&); + class Scene; typedef std::map<const IVolumeSlicer*, size_t> LayersIndex;
--- a/Framework/Widgets/TestCairoWidget.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/TestCairoWidget.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -99,7 +99,8 @@ IMouseTracker* TestCairoWidget::CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector<Touch>& touches) { UpdateStatusBar("Click"); return NULL;
--- a/Framework/Widgets/TestCairoWidget.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/TestCairoWidget.h Thu Apr 18 09:30:00 2019 +0200 @@ -51,7 +51,8 @@ virtual IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers); + KeyboardModifiers modifiers, + const std::vector<Touch>& touches); virtual void MouseWheel(MouseWheelDirection direction, int x,
--- a/Framework/Widgets/TestWorldSceneWidget.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/TestWorldSceneWidget.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -41,7 +41,8 @@ int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector<Touch>& touches) { if (statusBar) {
--- a/Framework/Widgets/WorldSceneWidget.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/WorldSceneWidget.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -23,6 +23,7 @@ #include "PanMouseTracker.h" #include "ZoomMouseTracker.h" +#include "PanZoomMouseTracker.h" #include <Core/Logging.h> #include <Core/OrthancException.h> @@ -72,11 +73,20 @@ } virtual void MouseMove(int x, - int y) + int y, + const std::vector<Touch>& displayTouches) { double sceneX, sceneY; view_.MapPixelCenterToScene(sceneX, sceneY, x, y); - tracker_->MouseMove(x, y, sceneX, sceneY); + + std::vector<Touch> sceneTouches; + for (size_t t = 0; t < displayTouches.size(); t++) + { + double sx, sy; + view_.MapPixelCenterToScene(sx, sy, (int)displayTouches[t].x, (int)displayTouches[t].y); + sceneTouches.push_back(Touch(sx, sy)); + } + tracker_->MouseMove(x, y, sceneX, sceneY, displayTouches, sceneTouches); } }; @@ -145,7 +155,8 @@ IMouseTracker* WorldSceneWidget::CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers) + KeyboardModifiers modifiers, + const std::vector<Touch>& touches) { double sceneX, sceneY; view_.MapPixelCenterToScene(sceneX, sceneY, x, y); @@ -155,7 +166,7 @@ if (interactor_) { - tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar())); + tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar(), touches)); } if (tracker.get() != NULL) @@ -164,17 +175,26 @@ } else if (hasDefaultMouseEvents_) { - switch (button) + printf("has default mouse events\n"); + if (touches.size() == 2) { - case MouseButton_Middle: - return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y)); + printf("2 touches !\n"); + return new SceneMouseTracker(view_, new PanZoomMouseTracker(*this, touches)); + } + else + { + switch (button) + { + case MouseButton_Middle: + return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y)); - case MouseButton_Right: - return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y)); + case MouseButton_Right: + return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y)); - default: - return NULL; - } + default: + return NULL; + } + } } else {
--- a/Framework/Widgets/WorldSceneWidget.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/WorldSceneWidget.h Thu Apr 18 09:30:00 2019 +0200 @@ -88,7 +88,8 @@ virtual IMouseTracker* CreateMouseTracker(MouseButton button, int x, int y, - KeyboardModifiers modifiers); + KeyboardModifiers modifiers, + const std::vector<Touch>& touches); virtual void MouseWheel(MouseWheelDirection direction, int x,
--- a/Framework/Widgets/ZoomMouseTracker.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/ZoomMouseTracker.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -61,7 +61,9 @@ void ZoomMouseTracker::MouseMove(int displayX, int displayY, double x, - double y) + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) { static const double MIN_ZOOM = -4; static const double MAX_ZOOM = 4;
--- a/Framework/Widgets/ZoomMouseTracker.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/Widgets/ZoomMouseTracker.h Thu Apr 18 09:30:00 2019 +0200 @@ -57,6 +57,8 @@ virtual void MouseMove(int displayX, int displayY, double x, - double y); + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); }; }
--- a/Framework/dev.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Framework/dev.h Thu Apr 18 09:30:00 2019 +0200 @@ -51,7 +51,7 @@ std::auto_ptr<DownloadStack> downloadStack_; bool computeRange_; size_t pendingSlices_; - + void ScheduleSliceDownload() { assert(downloadStack_.get() != NULL); @@ -70,20 +70,20 @@ if (!GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(), b.GetGeometry().GetNormal())) { - LOG(ERROR) << "Some slice in the volume image is not parallel to the others"; + LOG(ERROR) << "A slice in the volume image is not parallel to the others."; return false; } if (a.GetConverter().GetExpectedPixelFormat() != b.GetConverter().GetExpectedPixelFormat()) { - LOG(ERROR) << "The pixel format changes across the slices of the volume image"; + LOG(ERROR) << "The pixel format changes across the slices of the volume image."; return false; } if (a.GetWidth() != b.GetWidth() || a.GetHeight() != b.GetHeight()) { - LOG(ERROR) << "The width/height of the slices change across the volume image"; + LOG(ERROR) << "The slices dimensions (width/height) are varying throughout the volume image"; return false; } @@ -109,7 +109,7 @@ void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message) { assert(&message.GetOrigin() == &loader_); - + if (loader_.GetSliceCount() == 0) { LOG(ERROR) << "Empty volume image"; @@ -156,13 +156,13 @@ LOG(INFO) << "Creating a volume image of size " << width << "x" << height << "x" << loader_.GetSliceCount() << " in " << Orthanc::EnumerationToString(format); - image_.reset(new ImageBuffer3D(format, width, height, loader_.GetSliceCount(), computeRange_)); + image_.reset(new ImageBuffer3D(format, width, height, static_cast<unsigned int>(loader_.GetSliceCount()), computeRange_)); image_->SetAxialGeometry(loader_.GetSlice(0).GetGeometry()); image_->SetVoxelDimensions(loader_.GetSlice(0).GetPixelSpacingX(), loader_.GetSlice(0).GetPixelSpacingY(), spacingZ); image_->Clear(); - downloadStack_.reset(new DownloadStack(loader_.GetSliceCount())); + downloadStack_.reset(new DownloadStack(static_cast<unsigned int>(loader_.GetSliceCount()))); pendingSlices_ = loader_.GetSliceCount(); for (unsigned int i = 0; i < 4; i++) // Limit to 4 simultaneous downloads @@ -175,7 +175,7 @@ EmitMessage(ISlicedVolume::GeometryReadyMessage(*this)); } - + void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message) { assert(&message.GetOrigin() == &loader_); @@ -184,7 +184,7 @@ EmitMessage(ISlicedVolume::GeometryErrorMessage(*this)); } - + void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message) { assert(&message.GetOrigin() == &loader_); @@ -210,7 +210,7 @@ ScheduleSliceDownload(); } - + void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message) { assert(&message.GetOrigin() == &loader_); @@ -312,11 +312,11 @@ double sliceThickness_; CoordinateSystem3D reference_; DicomFrameConverter converter_; - + double ComputeAxialThickness(const OrthancVolumeImage& volume) const { double thickness; - + size_t n = volume.GetSliceCount(); if (n > 1) { @@ -342,11 +342,11 @@ return thickness; } } - + void SetupAxial(const OrthancVolumeImage& volume) { const Slice& axial = volume.GetSlice(0); - + width_ = axial.GetWidth(); height_ = axial.GetHeight(); depth_ = volume.GetSliceCount(); @@ -364,7 +364,7 @@ double axialThickness = ComputeAxialThickness(volume); width_ = axial.GetWidth(); - height_ = volume.GetSliceCount(); + height_ = static_cast<unsigned int>(volume.GetSliceCount()); depth_ = axial.GetHeight(); pixelSpacingX_ = axial.GetPixelSpacingX(); @@ -373,11 +373,11 @@ Vector origin = axial.GetGeometry().GetOrigin(); origin += (static_cast<double>(volume.GetSliceCount() - 1) * - axialThickness * axial.GetGeometry().GetNormal()); - + axialThickness * axial.GetGeometry().GetNormal()); + reference_ = CoordinateSystem3D(origin, axial.GetGeometry().GetAxisX(), - -axial.GetGeometry().GetNormal()); + - axial.GetGeometry().GetNormal()); } void SetupSagittal(const OrthancVolumeImage& volume) @@ -386,7 +386,7 @@ double axialThickness = ComputeAxialThickness(volume); width_ = axial.GetHeight(); - height_ = volume.GetSliceCount(); + height_ = static_cast<unsigned int>(volume.GetSliceCount()); depth_ = axial.GetWidth(); pixelSpacingX_ = axial.GetPixelSpacingY(); @@ -395,8 +395,8 @@ Vector origin = axial.GetGeometry().GetOrigin(); origin += (static_cast<double>(volume.GetSliceCount() - 1) * - axialThickness * axial.GetGeometry().GetNormal()); - + axialThickness * axial.GetGeometry().GetNormal()); + reference_ = CoordinateSystem3D(origin, axial.GetGeometry().GetAxisY(), axial.GetGeometry().GetNormal()); @@ -415,20 +415,20 @@ switch (projection) { - case VolumeProjection_Axial: - SetupAxial(volume); - break; + case VolumeProjection_Axial: + SetupAxial(volume); + break; - case VolumeProjection_Coronal: - SetupCoronal(volume); - break; + case VolumeProjection_Coronal: + SetupCoronal(volume); + break; - case VolumeProjection_Sagittal: - SetupSagittal(volume); - break; + case VolumeProjection_Sagittal: + SetupSagittal(volume); + break; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } } @@ -441,7 +441,7 @@ { return reference_.GetNormal(); } - + bool LookupSlice(size_t& index, const CoordinateSystem3D& slice) const { @@ -452,14 +452,14 @@ { return false; } - + double z = (reference_.ProjectAlongNormal(slice.GetOrigin()) - reference_.ProjectAlongNormal(reference_.GetOrigin())) / sliceThickness_; int s = static_cast<int>(boost::math::iround(z)); if (s < 0 || - s >= static_cast<int>(depth_)) + s >= static_cast<int>(depth_)) { return false; } @@ -507,9 +507,9 @@ RendererFactory(const Orthanc::ImageAccessor& frame, const Slice& slice, bool isFullQuality) : - frame_(frame), - slice_(slice), - isFullQuality_(isFullQuality) + frame_(frame), + slice_(slice), + isFullQuality_(isFullQuality) { } @@ -525,35 +525,35 @@ std::auto_ptr<VolumeImageGeometry> coronalGeometry_; std::auto_ptr<VolumeImageGeometry> sagittalGeometry_; - + bool IsGeometryReady() const { return axialGeometry_.get() != NULL; } - + void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message) { assert(&message.GetOrigin() == &volume_); - + // These 3 values are only used to speed up the IVolumeSlicer axialGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Axial)); coronalGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Coronal)); sagittalGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Sagittal)); - + EmitMessage(IVolumeSlicer::GeometryReadyMessage(*this)); } void OnGeometryError(const ISlicedVolume::GeometryErrorMessage& message) { assert(&message.GetOrigin() == &volume_); - + EmitMessage(IVolumeSlicer::GeometryErrorMessage(*this)); } void OnContentChanged(const ISlicedVolume::ContentChangedMessage& message) { assert(&message.GetOrigin() == &volume_); - + EmitMessage(IVolumeSlicer::ContentChangedMessage(*this)); } @@ -576,17 +576,17 @@ switch (projection) { - case VolumeProjection_Axial: - return *axialGeometry_; + case VolumeProjection_Axial: + return *axialGeometry_; - case VolumeProjection_Sagittal: - return *sagittalGeometry_; + case VolumeProjection_Sagittal: + return *sagittalGeometry_; - case VolumeProjection_Coronal: - return *coronalGeometry_; + case VolumeProjection_Coronal: + return *coronalGeometry_; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } } @@ -625,11 +625,11 @@ public: - VolumeImageMPRSlicer(MessageBroker& broker, + VolumeImageMPRSlicer(MessageBroker& broker, OrthancVolumeImage& volume) : - IVolumeSlicer(broker), - IObserver(broker), - volume_(volume) + IVolumeSlicer(broker), + IObserver(broker), + volume_(volume) { volume_.RegisterObserverCallback( new Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryReadyMessage> @@ -652,7 +652,7 @@ const CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE { VolumeProjection projection; - + if (!IsGeometryReady() || !DetectProjection(projection, viewportSlice)) { @@ -664,16 +664,15 @@ // we only consider one single reference slice (the one with index 0). std::auto_ptr<Slice> slice(GetProjectionGeometry(projection).GetSlice(0)); slice->GetExtent(points); - + return true; } } - virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE { VolumeProjection projection; - + if (IsGeometryReady() && DetectProjection(projection, viewportSlice)) { @@ -688,7 +687,7 @@ std::auto_ptr<Orthanc::Image> frame; { - ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, closest); + ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, static_cast<unsigned int>(closest)); // TODO Transfer ownership if non-axial, to avoid memcpy frame.reset(Orthanc::Image::Clone(reader.GetAccessor())); @@ -738,11 +737,15 @@ virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, const ViewportGeometry& view, MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar, + const std::vector<Touch>& touches) ORTHANC_OVERRIDE { - return NULL; + return NULL; } virtual void MouseOver(CairoContext& context, @@ -750,29 +753,29 @@ const ViewportGeometry& view, double x, double y, - IStatusBar* statusBar) + IStatusBar* statusBar) ORTHANC_OVERRIDE { } virtual void MouseWheel(WorldSceneWidget& widget, MouseWheelDirection direction, KeyboardModifiers modifiers, - IStatusBar* statusBar) + IStatusBar* statusBar) ORTHANC_OVERRIDE { int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1); switch (direction) { - case MouseWheelDirection_Up: - OffsetSlice(-scale); - break; + case MouseWheelDirection_Up: + OffsetSlice(-scale); + break; - case MouseWheelDirection_Down: - OffsetSlice(scale); - break; + case MouseWheelDirection_Down: + OffsetSlice(scale); + break; - default: - break; + default: + break; } } @@ -780,16 +783,16 @@ KeyboardKeys key, char keyChar, KeyboardModifiers modifiers, - IStatusBar* statusBar) + IStatusBar* statusBar) ORTHANC_OVERRIDE { switch (keyChar) { - case 's': - widget.FitContent(); - break; + case 's': + widget.FitContent(); + break; - default: - break; + default: + break; } } @@ -798,9 +801,9 @@ OrthancVolumeImage& volume, SliceViewerWidget& widget, VolumeProjection projection) : - IObserver(broker), - widget_(widget), - projection_(projection) + IObserver(broker), + widget_(widget), + projection_(projection) { widget.SetInteractor(*this); @@ -839,7 +842,7 @@ if (slice >= static_cast<int>(slices_->GetSliceCount())) { - slice = slices_->GetSliceCount() - 1; + slice = static_cast<unsigned int>(slices_->GetSliceCount()) - 1; } if (slice != static_cast<int>(slice_)) @@ -898,7 +901,7 @@ SliceViewerWidget& otherPlane_; public: - ReferenceLineSource(MessageBroker& broker, + ReferenceLineSource(MessageBroker& broker, SliceViewerWidget& otherPlane) : IVolumeSlicer(broker), otherPlane_(otherPlane) @@ -915,7 +918,7 @@ virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice) { Slice reference(viewportSlice, 0.001); - + Vector p, d; const CoordinateSystem3D& slice = otherPlane_.GetSlice(); @@ -935,7 +938,7 @@ viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d); const Extent2D extent = otherPlane_.GetSceneExtent(); - + if (GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, x1, y1, x2, y2, extent.GetX1(), extent.GetY1(),
--- a/Platforms/Generic/Oracle.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Generic/Oracle.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -70,7 +70,7 @@ { command.Execute(); } - catch (Orthanc::OrthancException& ex) + catch (Orthanc::OrthancException& /*ex*/) { // this is probably a curl error that has been triggered. We may just ignore it. // The command.success_ will stay at false and this will be handled in the command.Commit
--- a/Platforms/Wasm/Defaults.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Wasm/Defaults.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -49,7 +49,7 @@ viewports_.push_back(viewport); - printf("There are now %d viewports in C++\n", viewports_.size()); + printf("There are now %lu viewports in C++\n", viewports_.size()); viewport->SetStatusBar(statusBar_); @@ -64,7 +64,7 @@ void EMSCRIPTEN_KEEPALIVE ReleaseCppViewport(ViewportHandle viewport) { viewports_.remove_if([viewport](const std::shared_ptr<OrthancStone::WidgetViewport>& v) { return v.get() == viewport;}); - printf("There are now %d viewports in C++\n", viewports_.size()); + printf("There are now %lu viewports in C++\n", viewports_.size()); } void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle viewport) { @@ -191,7 +191,7 @@ return; // Unknown button } - viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None /* TODO */); + viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None, std::vector<OrthancStone::Touch>()); } @@ -222,9 +222,82 @@ int x, int y) { - viewport->MouseMove(x, y); + viewport->MouseMove(x, y, std::vector<OrthancStone::Touch>()); + } + + void GetTouchVector(std::vector<OrthancStone::Touch>& output, + int touchCount, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2) + { + // TODO: it might be nice to try to pass all the x0,y0 coordinates as arrays but that's not so easy to pass array between JS and C++ + if (touchCount > 0) + { + output.push_back(OrthancStone::Touch(x0, y0)); + } + if (touchCount > 1) + { + output.push_back(OrthancStone::Touch(x1, y1)); + } + if (touchCount > 2) + { + output.push_back(OrthancStone::Touch(x2, y2)); + } + } - + + void EMSCRIPTEN_KEEPALIVE ViewportTouchStart(ViewportHandle viewport, + int touchCount, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2) + { + printf("touch start with %d touches\n", touchCount); + + std::vector<OrthancStone::Touch> touches; + GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); + viewport->TouchStart(touches); + } + + void EMSCRIPTEN_KEEPALIVE ViewportTouchMove(ViewportHandle viewport, + int touchCount, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2) + { + printf("touch move with %d touches\n", touchCount); + + std::vector<OrthancStone::Touch> touches; + GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); + viewport->TouchMove(touches); + } + + void EMSCRIPTEN_KEEPALIVE ViewportTouchEnd(ViewportHandle viewport, + int touchCount, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2) + { + printf("touch end with %d touches remaining\n", touchCount); + + std::vector<OrthancStone::Touch> touches; + GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2); + viewport->TouchEnd(touches); + } + void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(ViewportHandle viewport, int key, const char* keyChar, @@ -269,23 +342,21 @@ viewport->MouseLeave(); } - const char* EMSCRIPTEN_KEEPALIVE SendMessageToStoneApplication(const char* message) + const char* EMSCRIPTEN_KEEPALIVE SendSerializedMessageToStoneApplication(const char* message) { static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread) - printf("SendMessageToStoneApplication\n"); + printf("SendSerializedMessageToStoneApplication\n"); printf("%s", message); if (applicationWasmAdapter.get() != NULL) { - printf("sending message to C++\n"); - applicationWasmAdapter->HandleMessageFromWeb(output, std::string(message)); + applicationWasmAdapter->HandleSerializedMessageFromWeb(output, std::string(message)); return output.c_str(); } - printf("This stone application does not have a Web Adapter"); + printf("This Stone application does not have a Web Adapter"); return NULL; } - #ifdef __cplusplus } #endif
--- a/Platforms/Wasm/Defaults.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Wasm/Defaults.h Thu Apr 18 09:30:00 2019 +0200 @@ -16,7 +16,8 @@ // JS methods accessible from C++ extern void ScheduleWebViewportRedrawFromCpp(ViewportHandle cppViewportHandle); - extern void UpdateStoneApplicationStatusFromCpp(const char* statusUpdateMessage); + extern void UpdateStoneApplicationStatusFromCppWithString(const char* statusUpdateMessage); + extern void UpdateStoneApplicationStatusFromCppWithSerializedMessage(const char* statusUpdateMessage); // C++ methods accessible from JS extern void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle cppViewportHandle);
--- a/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -2,50 +2,59 @@ #include "Framework/Toolbox/MessagingToolbox.h" #include "Framework/StoneException.h" -#include <Applications/Commands/BaseCommandBuilder.h> #include <stdio.h> #include "Platforms/Wasm/Defaults.h" namespace OrthancStone { - WasmPlatformApplicationAdapter::WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application) - : IObserver(broker), - application_(application) + WasmPlatformApplicationAdapter::WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application) + : IObserver(broker), + application_(application) + { + } + + void WasmPlatformApplicationAdapter::HandleSerializedMessageFromWeb(std::string& output, const std::string& input) + { + try { + application_.HandleSerializedMessage(input.c_str()); } - - void WasmPlatformApplicationAdapter::HandleMessageFromWeb(std::string& output, const std::string& input) + catch (StoneException& exc) + { + printf("Error while handling message from web (error code = %d):\n", exc.GetErrorCode()); + printf("While interpreting input: '%s'\n", input.c_str()); + output = std::string("ERROR : "); + } + catch (std::exception& exc) { - try - { - Json::Value inputJson; - // if the message is a command, build it and execute it - if (MessagingToolbox::ParseJson(inputJson, input.c_str(), input.size())) - { - std::unique_ptr<ICommand> command(application_.GetCommandBuilder().CreateFromJson(inputJson)); - if (command.get() == NULL) - printf("Could not parse command: '%s'\n", input.c_str()); - else - application_.ExecuteCommand(*command); - } - } - catch (StoneException& exc) - { - printf("Error while handling message from web (error code = %d):\n", exc.GetErrorCode()); - printf("While interpreting input: '%s'\n", input.c_str()); - } + printf("Error while handling message from web (error text = %s):\n", exc.what()); + printf("While interpreting input: '%s'\n", input.c_str()); + output = std::string("ERROR : "); + } + } + + void WasmPlatformApplicationAdapter::NotifyStatusUpdateFromCppToWebWithString(const std::string& statusUpdateMessage) + { + try + { + UpdateStoneApplicationStatusFromCppWithString(statusUpdateMessage.c_str()); } - - void WasmPlatformApplicationAdapter::NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) + catch (...) { - try - { - UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str()); - } - catch (...) - { - printf("Error while handling message to web\n"); - } + printf("Error while handling string message to web\n"); } + } + + void WasmPlatformApplicationAdapter::NotifyStatusUpdateFromCppToWebWithSerializedMessage(const std::string& statusUpdateMessage) + { + try + { + UpdateStoneApplicationStatusFromCppWithSerializedMessage(statusUpdateMessage.c_str()); + } + catch (...) + { + printf("Error while handling serialized message to web\n"); + } + } } \ No newline at end of file
--- a/Platforms/Wasm/WasmPlatformApplicationAdapter.h Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.h Thu Apr 18 09:30:00 2019 +0200 @@ -12,7 +12,8 @@ public: WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application); - virtual void HandleMessageFromWeb(std::string& output, const std::string& input); - virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage); + virtual void HandleSerializedMessageFromWeb(std::string& output, const std::string& input); + virtual void NotifyStatusUpdateFromCppToWebWithString(const std::string& statusUpdateMessage); + virtual void NotifyStatusUpdateFromCppToWebWithSerializedMessage(const std::string& statusUpdateMessage); }; } \ No newline at end of file
--- a/Platforms/Wasm/WasmWebService.js Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Wasm/WasmWebService.js Thu Apr 18 09:30:00 2019 +0200 @@ -37,7 +37,7 @@ WasmWebService_ScheduleLaterCachedSuccessNotification: function (brol) { setTimeout(function() { - WasmWebService_NotifyCachedSuccess(brol); + window.WasmWebService_NotifyCachedSuccess(brol); }, 0); }, @@ -62,10 +62,10 @@ var headers = _malloc(s.length + 1); stringToUTF8(s, headers, s.length + 1); - WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response), + window.WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response), this.response.byteLength, headers, payload); } else { - WasmWebService_NotifyError(callableFailure, url_, payload); + window.WasmWebService_NotifyError(callableFailure, url_, payload); } } } @@ -94,10 +94,10 @@ var headers = _malloc(s.length + 1); stringToUTF8(s, headers, s.length + 1); - WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response), + window.WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response), this.response.byteLength, headers, payload); } else { - WasmWebService_NotifyError(callableFailure, url_, payload); + window.WasmWebService_NotifyError(callableFailure, url_, payload); } } }
--- a/Platforms/Wasm/default-library.js Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Wasm/default-library.js Thu Apr 18 09:30:00 2019 +0200 @@ -7,9 +7,15 @@ CreateWasmViewportFromCpp: function(htmlCanvasId) { return window.CreateWasmViewport(htmlCanvasId); }, - // each time the StoneApplication updates its status, it may signal it through this method. i.e, to change the status of a button in the web interface - UpdateStoneApplicationStatusFromCpp: function(statusUpdateMessage) { + // each time the StoneApplication updates its status, it may signal it + // through this method. i.e, to change the status of a button in the web interface + UpdateStoneApplicationStatusFromCppWithString: function(statusUpdateMessage) { var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage); - UpdateWebApplication(statusUpdateMessage_); + window.UpdateWebApplicationWithString(statusUpdateMessage_); + }, + // same, but with a serialized message + UpdateStoneApplicationStatusFromCppWithSerializedMessage: function(statusUpdateMessage) { + var statusUpdateMessage_ = UTF8ToString(statusUpdateMessage); + window.UpdateWebApplicationWithSerializedMessage(statusUpdateMessage_); } });
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Platforms/Wasm/logger.ts Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,90 @@ +export enum LogSource { + Cpp, + Typescript +} + +export class StandardConsoleLogger { + public showSource: boolean = true; + + public debug(...args: any[]): void { + this._debug(LogSource.Typescript, ...args); + } + + public info(...args: any[]): void { + this._info(LogSource.Typescript, ...args); + } + + public infoFromCpp(message: string): void { + this._info(LogSource.Cpp, message); + } + + public warning(...args: any[]): void { + this._warning(LogSource.Typescript, ...args); + } + + public error(...args: any[]): void { + this._error(LogSource.Typescript, ...args); + } + + public errorFromCpp(message: string): void { + this._error(LogSource.Cpp, message); + } + + public _debug(source: LogSource, ...args: any[]): void { + var output = this.getOutput(source, args); + console.debug(...output); + } + + private _info(source: LogSource, ...args: any[]): void { + var output = this.getOutput(source, args); + console.info(...output); + } + + public _warning(source: LogSource, ...args: any[]): void { + var output = this.getOutput(source, args); + console.warn(...output); + } + + public _error(source: LogSource, ...args: any[]): void { + var output = this.getOutput(source, args); + console.error(...output); + } + + + private getOutput(source: LogSource, args: any[]): any[] { + var prefix = this.getPrefix(); + var prefixAndSource = []; + + if (prefix != null) { + prefixAndSource = [prefix]; + } + + if (this.showSource) { + if (source == LogSource.Typescript) { + prefixAndSource = [...prefixAndSource, "TS "]; + } else if (source == LogSource.Cpp) { + prefixAndSource = [...prefixAndSource, "C++"]; + } + } + + if (prefixAndSource.length > 0) { + prefixAndSource = [...prefixAndSource, "|"]; + } + + return [...prefixAndSource, ...args]; + } + + protected getPrefix(): string { + return null; + } +} + +export class TimeConsoleLogger extends StandardConsoleLogger { + protected getPrefix(): string { + let now = new Date(); + let timeString = now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0") + ":" + now.getSeconds().toString().padStart(2, "0") + "." + now.getMilliseconds().toString().padStart(3, "0"); + return timeString; + } +} + +export var defaultLogger: StandardConsoleLogger = new TimeConsoleLogger();
--- a/Platforms/Wasm/stone-framework-loader.ts Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Wasm/stone-framework-loader.ts Thu Apr 18 09:30:00 2019 +0200 @@ -2,6 +2,7 @@ * This file contains primitives to interface with WebAssembly and * with the Stone framework. **/ +import * as Logger from './logger' export declare type InitializationCallback = () => void; @@ -56,13 +57,13 @@ public static Initialize( verbose: boolean, callback: InitializationCallback) { - console.log('Initializing WebAssembly Module'); + Logger.defaultLogger.debug('Initializing WebAssembly Module'); // (<any> window). (<any> window).StoneFrameworkModule = { preRun: [ function() { - console.log('Loading the Stone Framework using WebAssembly'); + Logger.defaultLogger.debug('Loading the Stone Framework using WebAssembly'); } ], postRun: [ @@ -70,16 +71,16 @@ // 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'); + Logger.defaultLogger.debug('WebAssembly is ready'); Framework.singleton_ = new Framework(verbose); callback(); } ], print: function(text : string) { - console.log(text); + Logger.defaultLogger.infoFromCpp(text); }, printErr: function(text : string) { - console.error(text); + Logger.defaultLogger.errorFromCpp(text); }, totalDependencies: 0 };
--- a/Platforms/Wasm/tsconfig-stone.json Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Wasm/tsconfig-stone.json Thu Apr 18 09:30:00 2019 +0200 @@ -1,6 +1,7 @@ { "include" : [ "stone-framework-loader.ts", + "logger.ts", "wasm-application-runner.ts", "wasm-viewport.ts" ]
--- a/Platforms/Wasm/wasm-application-runner.ts Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Wasm/wasm-application-runner.ts Thu Apr 18 09:30:00 2019 +0200 @@ -1,5 +1,6 @@ import Stone = require('./stone-framework-loader'); import StoneViewport = require('./wasm-viewport'); +import * as Logger from './logger' if (!('WebAssembly' in window)) { alert('Sorry, your browser does not support WebAssembly :('); @@ -19,7 +20,7 @@ export var CreateCppViewport: Function = null; var ReleaseCppViewport: Function = null; var StartWasmApplication: Function = null; -export var SendMessageToStoneApplication: Function = null; +export var SendSerializedMessageToStoneApplication: Function = null; function DoAnimationThread() { if (WasmDoAnimation != null) { @@ -84,7 +85,7 @@ // the WebAssembly environment) and then, create and initialize the Wasm application Stone.Framework.Initialize(true, function () { - console.log("Connecting C++ methods to JS methods"); + Logger.defaultLogger.debug("Connecting C++ methods to JS methods"); SetStartupParameter = (<any> window).StoneFrameworkModule.cwrap('SetStartupParameter', null, ['string', 'string']); CreateWasmApplication = (<any> window).StoneFrameworkModule.cwrap('CreateWasmApplication', null, ['number']); @@ -99,14 +100,14 @@ // no need to put this into the globals for it's only used in this very module WasmDoAnimation = (<any> window).StoneFrameworkModule.cwrap('WasmDoAnimation', null, []); - SendMessageToStoneApplication = (<any> window).StoneFrameworkModule.cwrap('SendMessageToStoneApplication', 'string', ['string']); + SendSerializedMessageToStoneApplication = (<any> window).StoneFrameworkModule.cwrap('SendSerializedMessageToStoneApplication', 'string', ['string']); - console.log("Connecting C++ methods to JS methods - done"); + Logger.defaultLogger.debug("Connecting C++ methods to JS methods - done"); // Prevent scrolling document.body.addEventListener('touchmove', function (event) { event.preventDefault(); - }, false); + }, { passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface _InitializeWasmApplication(orthancBaseUrl); });
--- a/Platforms/Wasm/wasm-viewport.ts Wed Apr 17 17:57:50 2019 +0200 +++ b/Platforms/Wasm/wasm-viewport.ts Thu Apr 18 09:30:00 2019 +0200 @@ -1,5 +1,5 @@ import wasmApplicationRunner = require('./wasm-application-runner'); -//import stoneFrameworkLoader = require('./stone-framework-loader'); +import * as Logger from './logger' var isPendingRedraw = false; @@ -7,7 +7,7 @@ { if (!isPendingRedraw) { isPendingRedraw = true; - console.log('Scheduling a refresh of the viewport, as its content changed'); + Logger.defaultLogger.debug('Scheduling a refresh of the viewport, as its content changed'); window.requestAnimationFrame(function() { isPendingRedraw = false; WasmViewport.GetFromCppViewport(cppViewportHandle).Redraw(); @@ -30,13 +30,6 @@ (<any>window).CreateWasmViewport = CreateWasmViewport; -// 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 viewportsMapByCppHandle_ : Map<number, WasmViewport> = new Map<number, WasmViewport>(); // key = the C++ handle @@ -65,6 +58,9 @@ private ViewportMouseLeave : Function; private ViewportMouseWheel : Function; private ViewportKeyPressed : Function; + private ViewportTouchStart : Function; + private ViewportTouchMove : Function; + private ViewportTouchEnd : Function; private pimpl_ : any; // Private pointer to the underlying WebAssembly C++ object @@ -78,7 +74,7 @@ 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_, "'"); + Logger.defaultLogger.error("Can not create WasmViewport, did not find the canvas whose id is '", this.canvasId_, "'"); } this.context_ = this.htmlCanvas_.getContext('2d'); @@ -91,6 +87,9 @@ 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', 'number', 'string', 'number', 'number' ]); + this.ViewportTouchStart = this.module_.cwrap('ViewportTouchStart', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]); + this.ViewportTouchMove = this.module_.cwrap('ViewportTouchMove', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]); + this.ViewportTouchEnd = this.module_.cwrap('ViewportTouchEnd', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]); } public GetCppViewport() : number { @@ -101,7 +100,7 @@ if (WasmViewport.viewportsMapByCppHandle_[cppViewportHandle] !== undefined) { return WasmViewport.viewportsMapByCppHandle_[cppViewportHandle]; } - console.log("WasmViewport not found !"); + Logger.defaultLogger.error("WasmViewport not found !"); return undefined; } @@ -109,7 +108,7 @@ if (WasmViewport.viewportsMapByCanvasId_[canvasId] !== undefined) { return WasmViewport.viewportsMapByCanvasId_[canvasId]; } - console.log("WasmViewport not found !"); + Logger.defaultLogger.error("WasmViewport not found !"); return undefined; } @@ -126,13 +125,13 @@ this.imageData_.width, this.imageData_.height, this.renderingBuffer_) == 0) { - console.log('The rendering has failed'); + Logger.defaultLogger.error('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.module_.HEAPU8.buffer, this.renderingBuffer_, this.imageData_.width * this.imageData_.height * 4)); @@ -151,7 +150,7 @@ this.htmlCanvas_.width = this.htmlCanvas_.parentElement.offsetWidth; this.htmlCanvas_.height = this.htmlCanvas_.parentElement.offsetHeight; - console.log("resizing WasmViewport: ", this.htmlCanvas_.width, "x", this.htmlCanvas_.height); + Logger.defaultLogger.debug("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); @@ -197,7 +196,8 @@ 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 detect modifier keys*/); + + that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO detect modifier keys*/); }); this.htmlCanvas_.addEventListener('mousemove', function(event) { @@ -227,22 +227,62 @@ var y = event.pageY - this.offsetTop; that.ViewportMouseWheel(that.pimpl_, event.deltaY, x, y, event.ctrlKey); event.preventDefault(); - }); + }, {passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface - this.htmlCanvas_.addEventListener('touchstart', function(event) { + this.htmlCanvas_.addEventListener('touchstart', function(event: TouchEvent) { // don't propagate events to the whole body (this could zoom the entire page instead of zooming the viewport) event.preventDefault(); event.stopPropagation(); - that.ResetTouch(); - }); + // TODO: find a way to pass the coordinates as an array between JS and C++ + var x0 = 0; + var y0 = 0; + var x1 = 0; + var y1 = 0; + var x2 = 0; + var y2 = 0; + if (event.targetTouches.length > 0) { + x0 = event.targetTouches[0].pageX; + y0 = event.targetTouches[0].pageY; + } + if (event.targetTouches.length > 1) { + x1 = event.targetTouches[1].pageX; + y1 = event.targetTouches[1].pageY; + } + if (event.targetTouches.length > 2) { + x2 = event.targetTouches[2].pageX; + y2 = event.targetTouches[2].pageY; + } + + that.ViewportTouchStart(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2); + }, {passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface this.htmlCanvas_.addEventListener('touchend', function(event) { // don't propagate events to the whole body (this could zoom the entire page instead of zooming the viewport) event.preventDefault(); event.stopPropagation(); - that.ResetTouch(); + // TODO: find a way to pass the coordinates as an array between JS and C++ + var x0 = 0; + var y0 = 0; + var x1 = 0; + var y1 = 0; + var x2 = 0; + var y2 = 0; + if (event.targetTouches.length > 0) { + x0 = event.targetTouches[0].pageX; + y0 = event.targetTouches[0].pageY; + } + if (event.targetTouches.length > 1) { + x1 = event.targetTouches[1].pageX; + y1 = event.targetTouches[1].pageY; + } + if (event.targetTouches.length > 2) { + x2 = event.targetTouches[2].pageX; + y2 = event.targetTouches[2].pageY; + } + + that.ViewportTouchEnd(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2); }); this.htmlCanvas_.addEventListener('touchmove', function(event: TouchEvent) { @@ -251,53 +291,31 @@ event.preventDefault(); event.stopPropagation(); - // if (!that.touchGestureInProgress_) { - // // starting a new gesture - // that.touchCount_ = event.targetTouches.length; - // for (var t = 0; t < event.targetTouches.length; t++) { - // that.touchGestureLastCoordinates_.push([event.targetTouches[t].pageX, event.targetTouches[t].pageY]); - // } - // that.touchGestureInProgress_ = true; - // } else { - // // continuing a gesture - // // TODO: we shall probably forward all touches to the C++ code and let the "interactors/trackers" handle them - // if (that.touchCount_ == 1) { // consider it's a left mouse drag - - // } - // } - - // TODO: we shall probably forward all touches to the C++ code and let the "interactors/trackers" handle them - - if (that.touchTranslation_.length == 2) { // - var t = that.GetTouchTranslation(event); - that.ViewportMouseMove(that.pimpl_, t[0], t[1]); + // TODO: find a way to pass the coordinates as an array between JS and C++ + var x0 = 0; + var y0 = 0; + var x1 = 0; + var y1 = 0; + var x2 = 0; + var y2 = 0; + if (event.targetTouches.length > 0) { + x0 = event.targetTouches[0].pageX; + y0 = event.targetTouches[0].pageY; } - 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]); + if (event.targetTouches.length > 1) { + x1 = event.targetTouches[1].pageX; + y1 = event.targetTouches[1].pageY; } - 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_, - 0 /* left 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); - } + if (event.targetTouches.length > 2) { + x2 = event.targetTouches[2].pageX; + y2 = event.targetTouches[2].pageY; } - }); + + that.ViewportTouchMove(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2); + return; + + }, {passive: false}); // must not be passive if calling event.preventDefault, ie to cancel scroll or zoom of the whole interface } public ResetTouch() { @@ -308,10 +326,6 @@ this.touchTranslation_ = false; this.touchZoom_ = false; - - // this.touchGestureInProgress_ = false; - // this.touchCount_ = 0; - // this.touchGestureLastCoordinates_ = []; } public GetTouchTranslation(event) { @@ -334,7 +348,5 @@ d ]; } - + } - - \ No newline at end of file
--- a/README.md Wed Apr 17 17:57:50 2019 +0200 +++ b/README.md Thu Apr 18 09:30:00 2019 +0200 @@ -125,7 +125,10 @@ source code in an `orthanc` folder next to the Stone of Orthanc repository, please enter the following: +**Simple make generator with dynamic build** + ``` +# Please set $currentDir to the current folder mkdir -p ~/builds/orthanc-stone-build cd ~/builds/orthanc-stone-build cmake -DORTHANC_FRAMEWORK_SOURCE=path \ @@ -134,6 +137,28 @@ ~/orthanc-stone/Applications/Samples/ ``` +**Ninja generator with static SDL build (pwsh script)** + +``` +# Please yourself one level above the orthanc-stone and orthanc folders +if( -not (test-path stone_build_sdl)) { mkdir stone_build_sdl } +cd stone_build_sdl +cmake -G Ninja -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples/ +``` + +**Visual Studio 2017 generator with static SDL build (pwsh script)** + +``` +# The following will use Visual Studio 2017 to build the SDL samples +# in debug mode (with multiple compilers in parallel). NOTE: place +# yourself one level above the `orthanc-stone` and `orthanc` folders + +if( -not (test-path stone_build_sdl)) { mkdir stone_build_sdl } +cd stone_build_sdl +cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples/ +cmake --build . --config Debug +``` + If you are working on Windows, add the correct generator option to cmake to, for instance, generate msbuild files for Visual Studio. @@ -146,14 +171,29 @@ Building the Qt native samples (SimpleViewer only) under Windows: ------------------------------------------------------------------ + +**Visual Studio 2017 generator with static Qt build (pwsh script)** + For instance, if Qt is installed in `C:\Qt\5.12.0\msvc2017_64` -`cmake -DSTATIC_BUILD=ON -DCMAKE_PREFIX_PATH=C:\Qt\5.12.0\msvc2017_64 -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_QT=ON -G "Visual Studio 15 2017 Win64" ../orthanc-stone/Applications/Samples/` +``` +# The following will use Visual Studio 2017 to build the SDL samples +# in debug mode (with multiple compilers in parallel). NOTE: place +# yourself one level above the `orthanc-stone` and `orthanc` folders + +if( -not (test-path stone_build_qt)) { mkdir stone_build_qt } +cd stone_build_qt +cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DCMAKE_PREFIX_PATH=C:\Qt\5.12.0\msvc2017_64 -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_QT=ON ../orthanc-stone/Applications/Samples/ +cmake --build . --config Debug +``` Note: replace `$($pwd)` with the current directory when not using Powershell + + + Building the SDL native samples (SimpleViewer only) under Windows: ------------------------------------------------------------------ `cmake -DSTATIC_BUILD=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON -G "Visual Studio 15 2017 Win64" ../orthanc-stone/Applications/Samples/` @@ -196,3 +236,9 @@ url="https://doi.org/10.1007/s10278-018-0082-y" } +Various notes to be deleted +--------------------------- +class BaseCommand : public ICommand + +RadiographySceneCommand +GenericNoArgCommand
--- a/Resources/CMake/OrthancStoneConfiguration.cmake Wed Apr 17 17:57:50 2019 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Thu Apr 18 09:30:00 2019 +0200 @@ -32,6 +32,7 @@ include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake) include_directories(${ORTHANC_ROOT}) +include_directories(${ORTHANC_ROOT}/Core/Images) # hack for the numerous #include "../Enumerations.h" in Orthanc to work ##################################################################### @@ -103,6 +104,8 @@ add_definitions(-DORTHANC_ENABLE_NATIVE=0) endif() + + ##################################################################### ## Configuration of the C/C++ macros ##################################################################### @@ -175,10 +178,6 @@ set(APPLICATIONS_SOURCES ${ORTHANC_STONE_ROOT}/Applications/IStoneApplication.h ${ORTHANC_STONE_ROOT}/Applications/StoneApplicationContext.cpp - ${ORTHANC_STONE_ROOT}/Applications/Commands/BaseCommandBuilder.cpp - ${ORTHANC_STONE_ROOT}/Applications/Commands/ICommand.h - ${ORTHANC_STONE_ROOT}/Applications/Commands/ICommandExecutor.h - ${ORTHANC_STONE_ROOT}/Applications/Commands/ICommandBuilder.h ) if (NOT ORTHANC_SANDBOXED) @@ -259,9 +258,11 @@ ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyDicomLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerCropTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerMaskTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerMoveTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerResizeTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayerRotateTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyMaskLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyScene.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographySceneCommand.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographySceneReader.cpp @@ -298,6 +299,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ViewportGeometry.cpp ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoContext.cpp ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoSurface.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/IMouseTracker.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/IStatusBar.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/IViewport.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/WidgetViewport.cpp @@ -311,6 +313,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneMouseTracker.h ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayoutWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanMouseTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanZoomMouseTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/SliceViewerWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestCairoWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestWorldSceneWidget.cpp
--- a/Resources/CMake/OrthancStoneParameters.cmake Wed Apr 17 17:57:50 2019 +0200 +++ b/Resources/CMake/OrthancStoneParameters.cmake Thu Apr 18 09:30:00 2019 +0200 @@ -22,8 +22,6 @@ ## Import the parameters of the Orthanc Framework ##################################################################### -message("CMAKE_CURRENT_LIST_DIR = ${CMAKE_CURRENT_LIST_DIR}") - include(${CMAKE_CURRENT_LIST_DIR}/../../Resources/Orthanc/DownloadOrthancFramework.cmake) include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
--- a/Resources/CodeGeneration/Graveyard/runts.ps1 Wed Apr 17 17:57:50 2019 +0200 +++ b/Resources/CodeGeneration/Graveyard/runts.ps1 Thu Apr 18 09:30:00 2019 +0200 @@ -1,9 +1,3 @@ -# echo "+----------------------+" -# echo "| template.in.ts |" -# echo "+----------------------+" - -# tsc -t ES2015 .\template.in.ts; node .\template.in.js - # echo "+----------------------+" # echo "| playground.ts |" # echo "+----------------------+"
--- a/Resources/CodeGeneration/stonegentool.py Wed Apr 17 17:57:50 2019 +0200 +++ b/Resources/CodeGeneration/stonegentool.py Thu Apr 18 09:30:00 2019 +0200 @@ -6,6 +6,7 @@ from jinja2 import Template from io import StringIO import time +import datetime """ 1 2 3 4 5 6 7 @@ -38,30 +39,6 @@ # Return a single string: return '\n'.join(trimmed) - -class GenCode: - def __init__(self): - - # file-wide preamble (#include directives, comment...) - self.cppPreamble = StringIO() - - self.cppEnums = StringIO() - self.cppStructs = StringIO() - self.cppDispatcher = StringIO() - self.cppHandler = StringIO() - - # file-wide preamble (module directives, comment...) - self.tsPreamble = StringIO() - - self.tsEnums = StringIO() - self.tsStructs = StringIO() - self.tsDispatcher = StringIO() - self.tsHandler = StringIO() - - def FlattenToFiles(self, outputDir): - raise NotImplementedError() - - class JsonHelpers: """A set of utilities to perform JSON operations""" @@ -105,10 +82,12 @@ retVal = canonicalTypename retVal = retVal.replace("map", "std::map") retVal = retVal.replace("vector", "std::vector") + retVal = retVal.replace("set", "std::set") retVal = retVal.replace("string", "std::string") retVal = retVal.replace("int32", "int32_t") retVal = retVal.replace("float32", "float") retVal = retVal.replace("float64", "double") + retVal = retVal.replace("json", "Json::Value") return retVal def CanonToTs(canonicalTypename): @@ -120,10 +99,12 @@ retVal = canonicalTypename retVal = retVal.replace("map", "Map") retVal = retVal.replace("vector", "Array") + retVal = retVal.replace("set", "Set") retVal = retVal.replace("int32", "number") retVal = retVal.replace("float32", "number") retVal = retVal.replace("float64", "number") retVal = retVal.replace("bool", "boolean") + retVal = retVal.replace("json", "Object") return retVal def NeedsTsConstruction(enums, tsType): @@ -231,6 +212,9 @@ listOfDependentTypes = SplitListOfTypes(m.group(2)) return (True, m.group(1), listOfDependentTypes) +def GetStructFields(struct): + """This filters out the special metadata key from the struct fields""" + return [k for k in struct.keys() if k != '__handler'] def ComputeOrderFromTypeTree( ancestors, @@ -263,13 +247,15 @@ struct = schema[GetLongTypename(shortTypename, schema)] # The keys in the struct dict are the member names # The values in the struct dict are the member types - for field in struct.keys(): - # we fill the chain of dependent types (starting here) - ancestors.append(shortTypename) - ComputeOrderFromTypeTree( - ancestors, genOrder, struct[field], schema) - # don't forget to restore it! - ancestors.pop() + if struct: + # we reach this if struct is not None AND not empty + for field in GetStructFields(struct): + # we fill the chain of dependent types (starting here) + ancestors.append(shortTypename) + ComputeOrderFromTypeTree( + ancestors, genOrder, struct[field], schema) + # don't forget to restore it! + ancestors.pop() # now we're pretty sure our dependencies have been processed, # we can start marking our code for generation (it might @@ -335,6 +321,12 @@ # TODO: check struct fields are unique (in each struct) # TODO: check that in the source schema, there are spaces after each colon +nonTypeKeys = ['rootName'] +def GetTypesInSchema(schema): + """Returns the top schema keys that are actual type names""" + typeList = [k for k in schema if k not in nonTypeKeys] + return typeList + # +-----------------------+ # | Main processing logic | # +-----------------------+ @@ -352,13 +344,59 @@ # anything and we'll handle them, in their original declaration # order, at the start genOrder = [] - for fullName in schema.keys(): + for fullName in GetTypesInSchema(schema): if IsStructType(fullName): realName = GetShortTypename(fullName) ancestors = [] ComputeOrderFromTypeTree(ancestors, genOrder, realName, schema) return genOrder +def GetStructFields(fieldDict): + """Returns the regular (non __handler) struct fields""" + # the following happens for empty structs + if fieldDict == None: + return fieldDict + ret = {} + for k,v in fieldDict.items(): + if k != "__handler": + ret[k] = v + if k.startswith("__") and k != "__handler": + raise RuntimeError("Fields starting with __ (double underscore) are reserved names!") + return ret + +def GetStructMetadata(fieldDict): + """Returns the __handler struct fields (there are default values that + can be overridden by entries in the schema + Not tested because it's a fail-safe: if something is broken in this, + dependent projects will not build.""" + metadataDict = {} + metadataDict['handleInCpp'] = False + metadataDict['handleInTypescript'] = False + + if fieldDict != None: + for k,v in fieldDict.items(): + if k.startswith("__") and k != "__handler": + raise RuntimeError("Fields starting with __ (double underscore) are reserved names") + if k == "__handler": + if type(v) == list: + for i in v: + if i == "cpp": + metadataDict['handleInCpp'] = True + elif i == "ts": + metadataDict['handleInTypescript'] = True + else: + raise RuntimeError("Error in schema. Allowed values for __handler are \"cpp\" or \"ts\"") + elif type(v) == str: + if v == "cpp": + metadataDict['handleInCpp'] = True + elif v == "ts": + metadataDict['handleInTypescript'] = True + else: + raise RuntimeError("Error in schema. Allowed values for __handler are \"cpp\" or \"ts\" (or a list of both)") + else: + raise RuntimeError("Error in schema. Allowed values for __handler are \"cpp\" or \"ts\" (or a list of both)") + return metadataDict + def ProcessSchema(schema, genOrder): # sanity check CheckSchemaSchema(schema) @@ -403,7 +441,8 @@ fieldDict = schema["struct " + typename] struct = {} struct['name'] = typename - struct['fields'] = fieldDict + struct['fields'] = GetStructFields(fieldDict) + struct['__meta__'] = GetStructMetadata(fieldDict) structs.append(struct) templatingDict = {} @@ -437,7 +476,8 @@ nextCh = schemaText[i+1] if ch == ':': if not (nextCh == ' ' or nextCh == '\n'): - assert(False) + lineNumber = schemaText.count("\n",0,i) + 1 + raise RuntimeError("Error at line " + str(lineNumber) + " in the schema: colons must be followed by a space or a newline!") schema = yaml.load(schemaText) return schema @@ -445,11 +485,33 @@ obj = LoadSchema(fn) genOrder = ComputeRequiredDeclarationOrder(obj) templatingDict = ProcessSchema(obj, genOrder) + currentDT = datetime.datetime.now() + templatingDict['currentDatetime'] = str(currentDT) return templatingDict # +-----------------------+ # | ENTRY POINT | # +-----------------------+ +def Process(schemaFile, outDir): + tdico = GetTemplatingDictFromSchemaFilename(schemaFile) + + tsTemplateFile = \ + os.path.join(os.path.dirname(__file__), 'template.in.ts.j2') + template = MakeTemplateFromFile(tsTemplateFile) + renderedTsCode = template.render(**tdico) + outputTsFile = os.path.join( \ + outDir,str(tdico['rootName']) + "_generated.ts") + with open(outputTsFile,"wt",encoding='utf8') as outFile: + outFile.write(renderedTsCode) + + cppTemplateFile = \ + os.path.join(os.path.dirname(__file__), 'template.in.h.j2') + template = MakeTemplateFromFile(cppTemplateFile) + renderedCppCode = template.render(**tdico) + outputCppFile = os.path.join( \ + outDir, str(tdico['rootName']) + "_generated.hpp") + with open(outputCppFile,"wt",encoding='utf8') as outFile: + outFile.write(renderedCppCode) if __name__ == "__main__": import argparse @@ -483,237 +545,4 @@ args = parser.parse_args() schemaFile = args.input_schema outDir = args.out_dir - - tdico = GetTemplatingDictFromSchemaFilename(schemaFile) - - tsTemplateFile = \ - os.path.join(os.path.dirname(__file__), 'template.in.ts') - template = MakeTemplateFromFile(tsTemplateFile) - renderedTsCode = template.render(**tdico) - outputTsFile = os.path.join( \ - outDir,str(tdico['rootName']) + "_generated.ts") - with open(outputTsFile,"wt",encoding='utf8') as outFile: - outFile.write(renderedTsCode) - - cppTemplateFile = \ - os.path.join(os.path.dirname(__file__), 'template.in.h') - template = MakeTemplateFromFile(cppTemplateFile) - renderedCppCode = template.render(**tdico) - outputCppFile = os.path.join( \ - outDir, str(tdico['rootName']) + "_generated.hpp") - with open(outputCppFile,"wt",encoding='utf8') as outFile: - outFile.write(renderedCppCode) - -# def GenEnumDecl(genc: GenCode, fullName: str, schema: Dict) -> None: -# """Writes the enumerations in genc""" -# enumDict:Dict=schema[fullName] -# # jinja2 template -# j2cppEnum = Template(trim( -# """ {{fullName}} -# { -# {% for key in enumDict.keys()%} -# {{key}}, -# {%endfor%} -# }; -# """)) -# j2cppEnumR = j2cppEnum.render(locals()) -# genc.cppEnums.write(j2cppEnumR) - -# j2tsEnum = Template(trim( -# """ export {{fullName}} -# { -# {% for key in enumDict.keys()%} -# {{key}}, -# {%endfor%} -# }; -# """)) -# j2cppEnumR = j2cppEnum.render(locals()) -# genc.tsEnums.write(j2cppEnumR) - - - -# def GetSerializationCode(typename: str,valueName: str, tempName: str) -# if IsPrimitiveType(typename) or IsTemplateCollection(typename): -# # no need to write code for the primitive types or collections. -# # It is handled in C++ by the template functions and in TS by -# # the JSON.stringify code. -# elif IsStructType(typename): -# pass - -# def GenStructTypeDeclAndSerialize(genc: GenCode, type, schema) -> None: -# ###### -# # CPP -# ###### -# sampleCpp = """ struct Message1 -# { -# int32_t a; -# std::string b; -# EnumMonth0 c; -# bool d; -# }; - -# Json::Value StoneSerialize(const Message1& value) -# { -# Json::Value result(Json::objectValue); -# result["a"] = StoneSerialize(value.a); -# result["b"] = StoneSerialize(value.b); -# result["c"] = StoneSerialize(value.c); -# result["d"] = StoneSerialize(value.d); -# return result; -# } -# """ - - -# ###### -# # TS -# ###### -# sampleTs = """ -# { -# export class Message1 { -# a: number; -# b: string; -# c: EnumMonth0; -# d: boolean; -# public StoneSerialize(): string { -# let container: object = {}; -# container['type'] = 'Message1'; -# container['value'] = this; -# return JSON.stringify(container); -# } -# }; -# } -# """ - - - - -# tsText: StringIO = StringIO() -# cppText: StringIO = StringIO() - -# tsText.write("class %s\n" % typeDict["name"]) -# tsText.write("{\n") - -# cppText.write("struct %s\n" % typeDict["name"]) -# cppText.write("{\n") - -# """ - -# GenerateSerializationCode(typename,valueName) - -# primitives: -# ----------- -# int -# jsonValue val(objectInt); -# val.setValue("$name") -# parent.add(("$name",$name) -# double -# ... -# string -# ... - -# collections: -# ----------- -# dict { } - -# serializeValue() -# """ - -# for i in range(len(typeDict["fields"])): -# field = typeDict["fields"][i] -# name = field["name"] -# tsType = GetTypeScriptTypenameFromCanonical(field["type"]) -# tsText.write(" public %s %s;\n" % (tsType, name)) -# cppType = GetCppTypenameFromCanonical(field["type"]) -# cppText.write(" %s %s;\n" % (cppType, name)) - -# tsText.write("};\n\n") -# cppText.write("};\n\n") - -# genc.tsStructs.write(tsText.getvalue()) -# genc.cppStructs.write(cppText.getvalue()) - - -# def GenerateCodeFromTsTemplate(genc) - - -# +-----------------------+ -# | CODE GENERATION | -# +-----------------------+ - -# def GenPreambles(rootName: str, genc: GenCode) -> None: -# cppPreambleT = Template(trim( -# """// autogenerated by stonegentool on {{time.ctime()}} -# // for module {{rootName}} -# #include <cstdint> -# #include <string> -# #include <vector> -# #include <map> -# namespace {{rootName}} -# { -# Json::Value StoneSerialize(int32_t value) -# { -# Json::Value result(value); -# return result; -# } -# Json::Value StoneSerialize(double value) -# { -# Json::Value result(value); -# return result; -# } -# Json::Value StoneSerialize(bool value) -# { -# Json::Value result(value); -# return result; -# } -# Json::Value StoneSerialize(const std::string& value) -# { -# // the following is better than -# Json::Value result(value.data(),value.data()+value.size()); -# return result; -# } -# template<typename T> -# Json::Value StoneSerialize(const std::map<std::string,T>& value) -# { -# Json::Value result(Json::objectValue); - -# for (std::map<std::string, T>::const_iterator it = value.cbegin(); -# it != value.cend(); ++it) -# { -# // it->first it->second -# result[it->first] = StoneSerialize(it->second); -# } -# return result; -# } -# template<typename T> -# Json::Value StoneSerialize(const std::vector<T>& value) -# { -# Json::Value result(Json::arrayValue); -# for (size_t i = 0; i < value.size(); ++i) -# { -# result.append(StoneSerialize(value[i])); -# } -# return result; -# } -# """ -# cppPreambleR = cppPreambleT.render(locals()) -# genc.cppPreamble.write(cppPreambleR) - -# tsPreambleT = Template(trim( -# """// autogenerated by stonegentool on {{time.ctime()}} -# // for module {{rootName}} - -# namespace {{rootName}} -# { -# """ -# tsPreambleR = tsPreambleT.render(locals()) -# genc.tsPreamble.write(tsPreambleR) - -# def ComputeOrder_ProcessStruct( \ -# genOrder: List[str], name:str, schema: Dict[str, str]) -> None: -# # let's generate the code according to the -# struct = schema[name] - -# if not IsStructType(name): -# raise Exception(f'{typename} should start with "struct "') - - + Process(schemaFile, outDir)
--- a/Resources/CodeGeneration/stonegentool_test.py Wed Apr 17 17:57:50 2019 +0200 +++ b/Resources/CodeGeneration/stonegentool_test.py Thu Apr 18 09:30:00 2019 +0200 @@ -379,7 +379,7 @@ os.path.join(os.path.dirname(__file__), 'test_data', 'test1.yaml') tdico = GetTemplatingDictFromSchemaFilename(schemaFile) tsTemplateFile = \ - os.path.join(os.path.dirname(__file__), 'template.in.ts') + os.path.join(os.path.dirname(__file__), 'template.in.ts.j2') template = MakeTemplateFromFile(tsTemplateFile) renderedCode = template.render(**tdico) print(renderedCode)
--- a/Resources/CodeGeneration/template.in.h Wed Apr 17 17:57:50 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,365 +0,0 @@ -/* - 1 2 3 4 5 6 7 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 - -*/ - -#include <iostream> -#include <string> -#include <sstream> -#include <assert.h> -#include <memory> -#include <optional> -#include <json/json.h> - -//#define STONEGEN_NO_CPP11 1 - -#ifdef STONEGEN_NO_CPP11 -#define StoneSmartPtr std::auto_ptr -#else -#define StoneSmartPtr std::unique_ptr -#endif - -namespace {{rootName}} -{ - /** Throws in case of problem */ - void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue.asInt(); - } - - Json::Value _StoneSerializeValue(int32_t value) - { - Json::Value result(value); - return result; - } - - /** Throws in case of problem */ - void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue.asDouble(); - } - - Json::Value _StoneSerializeValue(double value) - { - Json::Value result(value); - return result; - } - - /** Throws in case of problem */ - void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue.asBool(); - } - - Json::Value _StoneSerializeValue(bool value) - { - Json::Value result(value); - return result; - } - - /** Throws in case of problem */ - void _StoneDeserializeValue( - std::string& destValue - , const Json::Value& jsonValue) - { - destValue = jsonValue.asString(); - } - - Json::Value _StoneSerializeValue(const std::string& value) - { - // the following is better than - Json::Value result(value.data(),value.data()+value.size()); - return result; - } - - std::string MakeIndent(int indent) - { - char* txt = reinterpret_cast<char*>(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!! - for(size_t i = 0; i < indent; ++i) - txt[i] = ' '; - txt[indent] = 0; - std::string retVal(txt); - free(txt); // NO EXCEPTION ABOVE !!!!!!!!!! - return retVal; - } - - // generic dumper - template<typename T> - std::ostream& StoneDumpValue(std::ostream& out, const T& value, int indent) - { - out << MakeIndent(indent) << value; - return out; - } - - // string dumper - std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, int indent) - { - out << MakeIndent(indent) << "\"" << value << "\""; - return out; - } - - /** Throws in case of problem */ - template<typename T> - void _StoneDeserializeValue( - std::map<std::string, T>& destValue, const Json::Value& jsonValue) - { - destValue.clear(); - for ( - Json::Value::const_iterator itr = jsonValue.begin(); - itr != jsonValue.end(); - itr++) - { - std::string key; - _StoneDeserializeValue(key, itr.key()); - - T innerDestValue; - _StoneDeserializeValue(innerDestValue, *itr); - - destValue[key] = innerDestValue; - } - } - - template<typename T> - Json::Value _StoneSerializeValue(const std::map<std::string,T>& value) - { - Json::Value result(Json::objectValue); - - for (typename std::map<std::string, T>::const_iterator it = value.cbegin(); - it != value.cend(); ++it) - { - // it->first it->second - result[it->first] = _StoneSerializeValue(it->second); - } - return result; - } - - template<typename T> - std::ostream& StoneDumpValue(std::ostream& out, const std::map<std::string,T>& value, int indent) - { - out << MakeIndent(indent) << "{\n"; - for (typename std::map<std::string, T>::const_iterator it = value.cbegin(); - it != value.cend(); ++it) - { - out << MakeIndent(indent+2) << "\"" << it->first << "\" : "; - StoneDumpValue(out, it->second, indent+2); - } - out << MakeIndent(indent) << "}\n"; - return out; - } - - /** Throws in case of problem */ - template<typename T> - void _StoneDeserializeValue( - std::vector<T>& destValue, const Json::Value& jsonValue) - { - destValue.clear(); - destValue.reserve(jsonValue.size()); - for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++) - { - T innerDestValue; - _StoneDeserializeValue(innerDestValue, jsonValue[i]); - destValue.push_back(innerDestValue); - } - } - - template<typename T> - Json::Value _StoneSerializeValue(const std::vector<T>& value) - { - Json::Value result(Json::arrayValue); - for (size_t i = 0; i < value.size(); ++i) - { - result.append(_StoneSerializeValue(value[i])); - } - return result; - } - - template<typename T> - std::ostream& StoneDumpValue(std::ostream& out, const std::vector<T>& value, int indent) - { - out << MakeIndent(indent) << "[\n"; - for (size_t i = 0; i < value.size(); ++i) - { - StoneDumpValue(out, value[i], indent+2); - } - out << MakeIndent(indent) << "]\n"; - return out; - } - - void StoneCheckSerializedValueTypeGeneric(const Json::Value& value) - { - if ((!value.isMember("type")) || (!value["type"].isString())) - { - std::stringstream ss; - ss << "Cannot deserialize value ('type' key invalid)"; - throw std::runtime_error(ss.str()); - } - } - - void StoneCheckSerializedValueType( - const Json::Value& value, std::string typeStr) - { - StoneCheckSerializedValueTypeGeneric(value); - - std::string actTypeStr = value["type"].asString(); - if (actTypeStr != typeStr) - { - std::stringstream ss; - ss << "Cannot deserialize type" << actTypeStr - << "into " << typeStr; - throw std::runtime_error(ss.str()); - } - } - - // end of generic methods - -// end of generic methods -{% for enum in enums%} - enum {{enum['name']}} { -{% for key in enum['fields']%} {{key}}, -{%endfor%} }; - - void _StoneDeserializeValue( - {{enum['name']}}& destValue, const Json::Value& jsonValue) - { - destValue = static_cast<{{enum['name']}}>(jsonValue.asInt64()); - } - - Json::Value _StoneSerializeValue(const {{enum['name']}}& value) - { - return Json::Value(static_cast<int64_t>(value)); - } - - std::ostream& StoneDumpValue(std::ostream& out, const {{enum['name']}}& value, int indent = 0) - { -{% for key in enum['fields']%} if( value == {{key}}) - { - out << MakeIndent(indent) << "{{key}}" << std::endl; - } -{%endfor%} return out; - } - -{%endfor%} -{% for struct in structs%} -#ifdef _MSC_VER -#pragma region {{struct['name']}} -#endif //_MSC_VER - - struct {{struct['name']}} - { -{% for key in struct['fields']%} {{CanonToCpp(struct['fields'][key])}} {{key}}; -{% endfor %} - {{struct['name']}}() - { -{% for key in struct['fields']%} {{key}} = {{CanonToCpp(struct['fields'][key])}}(); -{% endfor %} - } - }; - - void _StoneDeserializeValue({{struct['name']}}& destValue, const Json::Value& value) - { -{% for key in struct['fields']%} _StoneDeserializeValue(destValue.{{key}}, value["{{key}}"]); -{% endfor %} - } - - Json::Value _StoneSerializeValue(const {{struct['name']}}& value) - { - Json::Value result(Json::objectValue); -{% for key in struct['fields']%} result["{{key}}"] = _StoneSerializeValue(value.{{key}}); -{% endfor %} - return result; - } - - std::ostream& StoneDumpValue(std::ostream& out, const {{struct['name']}}& value, int indent = 0) - { - out << MakeIndent(indent) << "{\n"; -{% for key in struct['fields']%} out << MakeIndent(indent) << "{{key}}:\n"; - StoneDumpValue(out, value.{{key}},indent+2); - out << "\n"; -{% endfor %} - out << MakeIndent(indent) << "}\n"; - return out; - } - - void StoneDeserialize({{struct['name']}}& destValue, const Json::Value& value) - { - StoneCheckSerializedValueType(value, "{{rootName}}.{{struct['name']}}"); - _StoneDeserializeValue(destValue, value["value"]); - } - - Json::Value StoneSerialize(const {{struct['name']}}& value) - { - Json::Value result(Json::objectValue); - result["type"] = "{{rootName}}.{{struct['name']}}"; - result["value"] = _StoneSerializeValue(value); - return result; - } - -#ifdef _MSC_VER -#pragma endregion {{struct['name']}} -#endif //_MSC_VER -{% endfor %} -#ifdef _MSC_VER -#pragma region Dispatching code -#endif //_MSC_VER - - class IHandler - { - public: -{% for struct in structs%} virtual bool Handle(const {{struct['name']}}& value) = 0; -{% endfor %} }; - - /** Service function for StoneDispatchToHandler */ - bool StoneDispatchJsonToHandler( - const Json::Value& jsonValue, IHandler* handler) - { - StoneCheckSerializedValueTypeGeneric(jsonValue); - std::string type = jsonValue["type"].asString(); - if (type == "") - { - // this should never ever happen - throw std::runtime_error("Caught empty type while dispatching"); - } -{% for struct in structs%} else if (type == "{{rootName}}.{{struct['name']}}") - { - {{struct['name']}} value; - _StoneDeserializeValue(value, jsonValue["value"]); - return handler->Handle(value); - } -{% endfor %} else - { - return false; - } - } - - /** Takes a serialized type and passes this to the handler */ - bool StoneDispatchToHandler(std::string strValue, IHandler* handler) - { - Json::Value readValue; - - Json::CharReaderBuilder builder; - Json::CharReader* reader = builder.newCharReader(); - - StoneSmartPtr<Json::CharReader> ptr(reader); - - std::string errors; - - bool ok = reader->parse( - strValue.c_str(), - strValue.c_str() + strValue.size(), - &readValue, - &errors - ); - if (!ok) - { - std::stringstream ss; - ss << "Jsoncpp parsing error: " << errors; - throw std::runtime_error(ss.str()); - } - return StoneDispatchJsonToHandler(readValue, handler); - } - -#ifdef _MSC_VER -#pragma endregion Dispatching code -#endif //_MSC_VER -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/template.in.h.j2 Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,451 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 + +Generated on {{currentDatetime}} by stonegentool + +*/ +#pragma once + +#include <exception> +#include <iostream> +#include <string> +#include <sstream> +#include <assert.h> +#include <memory> +#include <set> +#include <json/json.h> + +//#define STONEGEN_NO_CPP11 1 + +#ifdef STONEGEN_NO_CPP11 +#define StoneSmartPtr std::auto_ptr +#else +#define StoneSmartPtr std::unique_ptr +#endif + +namespace {{rootName}} +{ + /** Throws in case of problem */ + inline void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asInt(); + } + + inline Json::Value _StoneSerializeValue(int32_t value) + { + Json::Value result(value); + return result; + } + + inline void _StoneDeserializeValue(Json::Value& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue; + } + + inline Json::Value _StoneSerializeValue(Json::Value value) + { + return value; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asDouble(); + } + + inline Json::Value _StoneSerializeValue(double value) + { + Json::Value result(value); + return result; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asBool(); + } + + inline Json::Value _StoneSerializeValue(bool value) + { + Json::Value result(value); + return result; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue( + std::string& destValue + , const Json::Value& jsonValue) + { + destValue = jsonValue.asString(); + } + + inline Json::Value _StoneSerializeValue(const std::string& value) + { + // the following is better than + Json::Value result(value.data(),value.data()+value.size()); + return result; + } + + inline std::string MakeIndent(size_t indent) + { + char* txt = reinterpret_cast<char*>(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!! + for(size_t i = 0; i < indent; ++i) + txt[i] = ' '; + txt[indent] = 0; + std::string retVal(txt); + free(txt); // NO EXCEPTION ABOVE !!!!!!!!!! + return retVal; + } + + // generic dumper + template<typename T> + std::ostream& StoneDumpValue(std::ostream& out, const T& value, size_t indent) + { + out << MakeIndent(indent) << value; + return out; + } + + // string dumper + inline std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, size_t indent) + { + out << MakeIndent(indent) << "\"" << value << "\""; + return out; + } + + /** Throws in case of problem */ + template<typename T> + void _StoneDeserializeValue( + std::map<std::string, T>& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + for ( + Json::Value::const_iterator itr = jsonValue.begin(); + itr != jsonValue.end(); + itr++) + { + std::string key; + _StoneDeserializeValue(key, itr.key()); + + T innerDestValue; + _StoneDeserializeValue(innerDestValue, *itr); + + destValue[key] = innerDestValue; + } + } + + template<typename T> + Json::Value _StoneSerializeValue(const std::map<std::string,T>& value) + { + Json::Value result(Json::objectValue); + + for (typename std::map<std::string, T>::const_iterator it = value.cbegin(); + it != value.cend(); ++it) + { + // it->first it->second + result[it->first] = _StoneSerializeValue(it->second); + } + return result; + } + + template<typename T> + std::ostream& StoneDumpValue(std::ostream& out, const std::map<std::string,T>& value, size_t indent) + { + out << MakeIndent(indent) << "{\n"; + for (typename std::map<std::string, T>::const_iterator it = value.cbegin(); + it != value.cend(); ++it) + { + out << MakeIndent(indent+2) << "\"" << it->first << "\" : "; + StoneDumpValue(out, it->second, indent+2); + } + out << MakeIndent(indent) << "}\n"; + return out; + } + + /** Throws in case of problem */ + template<typename T> + void _StoneDeserializeValue( + std::vector<T>& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + destValue.reserve(jsonValue.size()); + for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++) + { + T innerDestValue; + _StoneDeserializeValue(innerDestValue, jsonValue[i]); + destValue.push_back(innerDestValue); + } + } + + template<typename T> + Json::Value _StoneSerializeValue(const std::vector<T>& value) + { + Json::Value result(Json::arrayValue); + for (size_t i = 0; i < value.size(); ++i) + { + result.append(_StoneSerializeValue(value[i])); + } + return result; + } + + template<typename T> + std::ostream& StoneDumpValue(std::ostream& out, const std::vector<T>& value, size_t indent) + { + out << MakeIndent(indent) << "[\n"; + for (size_t i = 0; i < value.size(); ++i) + { + StoneDumpValue(out, value[i], indent+2); + } + out << MakeIndent(indent) << "]\n"; + return out; + } + + /** Throws in case of problem */ + template<typename T> + void _StoneDeserializeValue( + std::set<T>& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++) + { + T innerDestValue; + _StoneDeserializeValue(innerDestValue, jsonValue[i]); + destValue.insert(innerDestValue); + } + } + + template<typename T> + Json::Value _StoneSerializeValue(const std::set<T>& value) + { + Json::Value result(Json::arrayValue); + for (typename std::set<T>::const_iterator it = value.begin(); it != value.end(); ++it) + { + result.append(_StoneSerializeValue(*it)); + } + return result; + } + + template<typename T> + std::ostream& StoneDumpValue(std::ostream& out, const std::set<T>& value, size_t indent) + { + out << MakeIndent(indent) << "[\n"; + for (typename std::set<T>::const_iterator it = value.begin(); it != value.end(); ++it) + { + StoneDumpValue(out, *it, indent+2); + } + out << MakeIndent(indent) << "]\n"; + return out; + } + + inline void StoneCheckSerializedValueTypeGeneric(const Json::Value& value) + { + if ((!value.isMember("type")) || (!value["type"].isString())) + { + std::stringstream ss; + ss << "Cannot deserialize value ('type' key invalid)"; + throw std::runtime_error(ss.str()); + } + } + + inline void StoneCheckSerializedValueType( + const Json::Value& value, std::string typeStr) + { + StoneCheckSerializedValueTypeGeneric(value); + + std::string actTypeStr = value["type"].asString(); + if (actTypeStr != typeStr) + { + std::stringstream ss; + ss << "Cannot deserialize type" << actTypeStr + << "into " << typeStr; + throw std::runtime_error(ss.str()); + } + } + + // end of generic methods + +// end of generic methods +{% for enum in enums%} + enum {{enum['name']}} { +{% for key in enum['fields']%} {{enum['name']}}_{{key}}, +{%endfor%} }; + + inline std::string ToString(const {{enum['name']}}& value) + { +{% for key in enum['fields']%} if( value == {{enum['name']}}_{{key}}) + { + return std::string("{{key}}"); + } +{%endfor%} std::stringstream ss; + ss << "Value \"" << value << "\" cannot be converted to {{enum['name']}}. Possible values are: " +{% for key in enum['fields']%} << " {{key}} = " << static_cast<int64_t>({{enum['name']}}_{{key}}) << ", " +{% endfor %} << std::endl; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + inline void FromString({{enum['name']}}& value, std::string strValue) + { +{% for key in enum['fields']%} if( strValue == std::string("{{key}}") ) + { + value = {{enum['name']}}_{{key}}; + return; + } +{%endfor%} + std::stringstream ss; + ss << "String \"" << strValue << "\" cannot be converted to {{enum['name']}}. Possible values are: {% for key in enum['fields']%}{{key}} {% endfor %}"; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + + inline void _StoneDeserializeValue( + {{enum['name']}}& destValue, const Json::Value& jsonValue) + { + FromString(destValue, jsonValue.asString()); + } + + inline Json::Value _StoneSerializeValue(const {{enum['name']}}& value) + { + std::string strValue = ToString(value); + return Json::Value(strValue); + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const {{enum['name']}}& value, size_t indent = 0) + { +{% for key in enum['fields']%} if( value == {{enum['name']}}_{{key}}) + { + out << MakeIndent(indent) << "{{key}}" << std::endl; + } +{%endfor%} return out; + } + +{%endfor%} +{% for struct in structs%} +#ifdef _MSC_VER +#pragma region {{struct['name']}} +#endif //_MSC_VER + + struct {{struct['name']}} + { +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} {{CanonToCpp(struct['fields'][key])}} {{key}}; +{% endfor %}{% endif %}{% endif %} + {{struct['name']}}({% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}{{CanonToCpp(struct['fields'][key])}} {{key}} = {{CanonToCpp(struct['fields'][key])}}(){{ ", " if not loop.last }}{% endfor %}{% endif %}{% endif %}) + { +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} this->{{key}} = {{key}}; +{% endfor %}{% endif %}{% endif %} } + }; + + inline void _StoneDeserializeValue({{struct['name']}}& destValue, const Json::Value& value) + { +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} _StoneDeserializeValue(destValue.{{key}}, value["{{key}}"]); +{% endfor %}{% endif %}{% endif %} } + + inline Json::Value _StoneSerializeValue(const {{struct['name']}}& value) + { + Json::Value result(Json::objectValue); +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} result["{{key}}"] = _StoneSerializeValue(value.{{key}}); +{% endfor %}{% endif %}{% endif %} + return result; + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const {{struct['name']}}& value, size_t indent = 0) + { + out << MakeIndent(indent) << "{\n"; +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} out << MakeIndent(indent) << "{{key}}:\n"; + StoneDumpValue(out, value.{{key}},indent+2); + out << "\n"; +{% endfor %}{% endif %}{% endif %} + out << MakeIndent(indent) << "}\n"; + return out; + } + + inline void StoneDeserialize({{struct['name']}}& destValue, const Json::Value& value) + { + StoneCheckSerializedValueType(value, "{{rootName}}.{{struct['name']}}"); + _StoneDeserializeValue(destValue, value["value"]); + } + + inline Json::Value StoneSerializeToJson(const {{struct['name']}}& value) + { + Json::Value result(Json::objectValue); + result["type"] = "{{rootName}}.{{struct['name']}}"; + result["value"] = _StoneSerializeValue(value); + return result; + } + + inline std::string StoneSerialize(const {{struct['name']}}& value) + { + Json::Value resultJson = StoneSerializeToJson(value); + std::string resultStr = resultJson.toStyledString(); + return resultStr; + } + +#ifdef _MSC_VER +#pragma endregion {{struct['name']}} +#endif //_MSC_VER +{% endfor %} +#ifdef _MSC_VER +#pragma region Dispatching code +#endif //_MSC_VER + + class IHandler + { + public: +{% for struct in structs%}{% if struct['__meta__'].handleInCpp %} virtual bool Handle(const {{struct['name']}}& value) = 0; +{% endif %}{% endfor %} }; + + /** Service function for StoneDispatchToHandler */ + inline bool StoneDispatchJsonToHandler( + const Json::Value& jsonValue, IHandler* handler) + { + StoneCheckSerializedValueTypeGeneric(jsonValue); + std::string type = jsonValue["type"].asString(); + if (type == "") + { + // this should never ever happen + throw std::runtime_error("Caught empty type while dispatching"); + } +{% for struct in structs%}{% if struct['__meta__'].handleInCpp %} else if (type == "{{rootName}}.{{struct['name']}}") + { + {{struct['name']}} value; + _StoneDeserializeValue(value, jsonValue["value"]); + return handler->Handle(value); + } +{% endif %}{% endfor %} else + { + return false; + } + } + + /** Takes a serialized type and passes this to the handler */ + inline bool StoneDispatchToHandler(std::string strValue, IHandler* handler) + { + Json::Value readValue; + + Json::CharReaderBuilder builder; + Json::CharReader* reader = builder.newCharReader(); + + StoneSmartPtr<Json::CharReader> ptr(reader); + + std::string errors; + + bool ok = reader->parse( + strValue.c_str(), + strValue.c_str() + strValue.size(), + &readValue, + &errors + ); + if (!ok) + { + std::stringstream ss; + ss << "Jsoncpp parsing error: " << errors; + throw std::runtime_error(ss.str()); + } + return StoneDispatchJsonToHandler(readValue, handler); + } + +#ifdef _MSC_VER +#pragma endregion Dispatching code +#endif //_MSC_VER +}
--- a/Resources/CodeGeneration/template.in.ts Wed Apr 17 17:57:50 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -/* - 1 2 3 4 5 6 7 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -*/ - -function StoneCheckSerializedValueType(value: any, typeStr: string) -{ - StoneCheckSerializedValueTypeGeneric(value); - - if (value['type'] != typeStr) - { - throw new Error( - `Cannot deserialize type ${value['type']} into ${typeStr}`); - } -} - -function isString(val: any) :boolean -{ - return ((typeof val === 'string') || (val instanceof String)); -} - -function StoneCheckSerializedValueTypeGeneric(value: any) -{ - // console.//log("+-------------------------------------------------+"); - // console.//log("| StoneCheckSerializedValueTypeGeneric |"); - // console.//log("+-------------------------------------------------+"); - // console.//log("value = "); - // console.//log(value); - if ( (!('type' in value)) || (!isString(value.type)) ) - { - throw new Error( - "Cannot deserialize value ('type' key invalid)"); - } -} - -// end of generic methods -{% for enum in enums%} -export enum {{enum['name']}} { - {% for key in enum['fields']%}{{key}}, - {%endfor%} -}; -{%endfor%} - -{% for struct in structs%} export class {{struct['name']}} { -{% for key in struct['fields']%} {{key}}:{{CanonToTs(struct['fields'][key])}}; -{% endfor %} - constructor() { -{% for key in struct['fields']%}{% if NeedsTsConstruction(enums,CanonToTs(struct['fields'][key])) %} this.{{key}} = new {{CanonToTs(struct['fields'][key])}}(); -{% endif %}{% endfor %} } - - public StoneSerialize(): string { - let container: object = {}; - container['type'] = '{{rootName}}.{{struct['name']}}'; - container['value'] = this; - return JSON.stringify(container); - } - - public static StoneDeserialize(valueStr: string) : {{struct['name']}} - { - let value: any = JSON.parse(valueStr); - StoneCheckSerializedValueType(value, '{{rootName}}.{{struct['name']}}'); - let result: {{struct['name']}} = value['value'] as {{struct['name']}}; - return result; - } -} - -{% endfor %} - -export interface IHandler { - {% for struct in structs%} Handle{{struct['name']}}(value: {{struct['name']}}): boolean; - {% endfor %} -}; - -/** Service function for StoneDispatchToHandler */ -export function StoneDispatchJsonToHandler( - jsonValue: any, handler: IHandler): boolean -{ - StoneCheckSerializedValueTypeGeneric(jsonValue); - let type: string = jsonValue["type"]; - if (type == "") - { - // this should never ever happen - throw new Error("Caught empty type while dispatching"); - } -{% for struct in structs%} else if (type == "{{rootName}}.{{struct['name']}}") - { - let value = jsonValue["value"] as {{struct['name']}}; - return handler.Handle{{struct['name']}}(value); - } -{% endfor %} - else - { - return false; - } -} - -/** Takes a serialized type and passes this to the handler */ -export function StoneDispatchToHandler( - strValue: string, handler: IHandler): boolean -{ - // console.//log("+------------------------------------------------+"); - // console.//log("| StoneDispatchToHandler |"); - // console.//log("+------------------------------------------------+"); - // console.//log("strValue = "); - // console.//log(strValue); - let jsonValue: any = JSON.parse(strValue) - return StoneDispatchJsonToHandler(jsonValue, handler); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CodeGeneration/template.in.ts.j2 Thu Apr 18 09:30:00 2019 +0200 @@ -0,0 +1,134 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 + +Generated on {{currentDatetime}} by stonegentool + +*/ + +function StoneCheckSerializedValueType(value: any, typeStr: string) +{ + StoneCheckSerializedValueTypeGeneric(value); + + if (value['type'] != typeStr) + { + throw new Error( + `Cannot deserialize type ${value['type']} into ${typeStr}`); + } +} + +function isString(val: any) :boolean +{ + return ((typeof val === 'string') || (val instanceof String)); +} + +function StoneCheckSerializedValueTypeGeneric(value: any) +{ + // console.//log("+-------------------------------------------------+"); + // console.//log("| StoneCheckSerializedValueTypeGeneric |"); + // console.//log("+-------------------------------------------------+"); + // console.//log("value = "); + // console.//log(value); + if ( (!('type' in value)) || (!isString(value.type)) ) + { + throw new Error( + "Cannot deserialize value ('type' key invalid)"); + } +} + +// end of generic methods +{% for enum in enums%} +export enum {{enum['name']}} { +{% for key in enum['fields']%} {{key}} = "{{key}}"{% if not loop.last %},{%endif%} +{%endfor%}}; + +export function {{enum['name']}}_FromString(strValue:string) : {{enum['name']}} +{ +{% for key in enum['fields'] %} if( strValue == "{{key}}" ) + { + return {{enum['name']}}.{{key}}; + } +{%endfor%} + let msg : string = `String ${strValue} cannot be converted to {{enum['name']}}. Possible values are: {% for key in enum['fields']%}{{key}}{% if not loop.last %}, {%endif%}{% endfor %}`; + throw new Error(msg); +} + +export function {{enum['name']}}_ToString(value:{{enum['name']}}) : string +{ +{% for key in enum['fields'] %} if( value == {{enum['name']}}.{{key}} ) + { + return "{{key}}"; + } +{%endfor%} + let msg : string = `Value ${value} cannot be converted to {{enum['name']}}. Possible values are: `; +{% for key in enum['fields']%} { + let _{{key}}_enumValue : string = {{enum['name']}}.{{key}}; // enums are strings in stonecodegen, so this will work. + let msg_{{key}} : string = `{{key}} (${_{{key}}_enumValue}){% if not loop.last %}, {%endif%}`; + msg = msg + msg_{{key}}; + } +{%endfor%} throw new Error(msg); +} +{%endfor%} + + +{% for struct in structs%}export class {{struct['name']}} { +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%} {{key}}:{{CanonToTs(struct['fields'][key])}}; +{% endfor %}{% endif %}{% endif %} + constructor() { +{% if struct %}{% if struct['fields'] %}{% for key in struct['fields']%}{% if NeedsTsConstruction(enums,CanonToTs(struct['fields'][key])) %} this.{{key}} = new {{CanonToTs(struct['fields'][key])}}(); +{% endif %}{% endfor %}{% endif %}{% endif %} } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = '{{rootName}}.{{struct['name']}}'; + container['value'] = this; + return JSON.stringify(container); + } + + public static StoneDeserialize(valueStr: string) : {{struct['name']}} + { + let value: any = JSON.parse(valueStr); + StoneCheckSerializedValueType(value, '{{rootName}}.{{struct['name']}}'); + let result: {{struct['name']}} = value['value'] as {{struct['name']}}; + return result; + } +} +{% endfor %} +export interface IHandler { +{% for struct in structs%}{% if struct['__meta__'].handleInTypescript %} Handle{{struct['name']}}(value: {{struct['name']}}): boolean; +{% endif %}{% endfor %}}; + +/** Service function for StoneDispatchToHandler */ +export function StoneDispatchJsonToHandler( + jsonValue: any, handler: IHandler): boolean +{ + StoneCheckSerializedValueTypeGeneric(jsonValue); + let type: string = jsonValue["type"]; + if (type == "") + { + // this should never ever happen + throw new Error("Caught empty type while dispatching"); + } +{% for struct in structs%}{% if struct['__meta__'].handleInTypescript %} else if (type == "{{rootName}}.{{struct['name']}}") + { + let value = jsonValue["value"] as {{struct['name']}}; + return handler.Handle{{struct['name']}}(value); + } +{% endif %}{% endfor %} else + { + return false; + } +} + +/** Takes a serialized type and passes this to the handler */ +export function StoneDispatchToHandler( + strValue: string, handler: IHandler): boolean +{ + // console.//log("+------------------------------------------------+"); + // console.//log("| StoneDispatchToHandler |"); + // console.//log("+------------------------------------------------+"); + // console.//log("strValue = "); + // console.//log(strValue); + let jsonValue: any = JSON.parse(strValue) + return StoneDispatchJsonToHandler(jsonValue, handler); +}
--- a/Resources/CodeGeneration/testCppHandler/CMakeLists.txt Wed Apr 17 17:57:50 2019 +0200 +++ b/Resources/CodeGeneration/testCppHandler/CMakeLists.txt Thu Apr 18 09:30:00 2019 +0200 @@ -4,7 +4,7 @@ set(testCppHandler_Codegen_Deps ${CMAKE_CURRENT_LIST_DIR}/../test_data/test1.yaml - ${CMAKE_CURRENT_LIST_DIR}/../template.in.h + ${CMAKE_CURRENT_LIST_DIR}/../template.in.h.j2 ) add_custom_command(
--- a/Resources/CodeGeneration/testWasmIntegrated/CMakeLists.txt Wed Apr 17 17:57:50 2019 +0200 +++ b/Resources/CodeGeneration/testWasmIntegrated/CMakeLists.txt Thu Apr 18 09:30:00 2019 +0200 @@ -14,8 +14,8 @@ set(testWasmIntegratedCpp_Codegen_Deps ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml - ${CMAKE_CURRENT_LIST_DIR}/../template.in.h - ${CMAKE_CURRENT_LIST_DIR}/../template.in.ts + ${CMAKE_CURRENT_LIST_DIR}/../template.in.h.j2 + ${CMAKE_CURRENT_LIST_DIR}/../template.in.ts.j2 ) set(jsoncppRootDir ${CMAKE_CURRENT_LIST_DIR}/jsoncpp-1.8.4) @@ -23,7 +23,7 @@ add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.hpp ${CMAKE_CURRENT_BINARY_DIR}/testWasmIntegratedCpp_generated.ts COMMAND python3 ${CMAKE_CURRENT_LIST_DIR}/../stonegentool.py -o ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml - DEPENDS ${testCppHandler_Codegen_Deps} + DEPENDS ${testCppHandler_Codegen_Deps} ${CMAKE_CURRENT_LIST_DIR}/testWasmIntegratedCpp_api.yaml ) add_executable(testWasmIntegratedCpp
--- a/Resources/CodeGeneration/testWasmIntegrated/main.cpp Wed Apr 17 17:57:50 2019 +0200 +++ b/Resources/CodeGeneration/testWasmIntegrated/main.cpp Thu Apr 18 09:30:00 2019 +0200 @@ -8,6 +8,45 @@ int main() { std::cout << "Hello world from testWasmIntegrated! (this is sent from C++)" << std::endl; + try + { + const char* jsonData = R"bgo({"definition": + { + "val" : [ "berk", 42 ], + "zozo" : { "23": "zloutch", "lalala": 42} + } + })bgo"; + std::string strValue(jsonData); + + Json::Value readValue; + + Json::CharReaderBuilder builder; + Json::CharReader* reader = builder.newCharReader(); + + StoneSmartPtr<Json::CharReader> ptr(reader); + + std::string errors; + + bool ok = reader->parse( + strValue.c_str(), + strValue.c_str() + strValue.size(), + &readValue, + &errors + ); + if (!ok) + { + std::stringstream ss; + ss << "Jsoncpp parsing error: " << errors; + throw std::runtime_error(ss.str()); + } + std::cout << "Json parsing OK" << std::endl; + std::cout << readValue << std::endl; + } + catch(std::exception& e) + { + std::cout << "Json parsing THROW" << std::endl; + std::cout << "e.what() = " << e.what() << std::endl; + } } extern "C" void SendMessageFromCppJS(const char* message);
--- a/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegrated.ts Wed Apr 17 17:57:50 2019 +0200 +++ b/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegrated.ts Thu Apr 18 09:30:00 2019 +0200 @@ -57,6 +57,7 @@ struct C: someBs: vector<B> ddd: vector<string> + definition: vector<json> struct A: someStrings: vector<string> @@ -76,6 +77,7 @@ titi: map<string, string> lulu: map<string, Message1> movieType: MovieType + definition: json enum MovieType: - RomCom @@ -106,14 +108,14 @@ { "a" : 43, "b" : "Sandrine", - "c" : 2, + "c" : "March", "d" : true }, "55" : { "a" : 42, "b" : "Benjamin", - "c" : 0, + "c" : "January", "d" : false } }, @@ -122,13 +124,13 @@ { "a" : 42, "b" : "Benjamin", - "c" : 0, + "c" : "March", "d" : false }, { "a" : 43, "b" : "Sandrine", - "c" : 2, + "c" : "January", "d" : false } ], @@ -142,7 +144,16 @@ [ "Mercadet", "Poisson" - ] + ], + "definition": + { + "val" : [ "berk", 42 ], + "zozo" : + { + "23": "zloutch", + "lalala": 42 + } + } } }`; stockSerializedMessages["Test 2"] = ` { @@ -224,8 +235,8 @@ // this method is called "from the C++ code" when the StoneApplication is updated. // it can be used to update the UI of the application -function UpdateWebApplication(statusUpdateMessageString: string) { - console.log("updating web application: ", statusUpdateMessageString); +function UpdateWebApplicationWithString(statusUpdateMessageString: string) { + console.log("updating web application (string): ", statusUpdateMessageString); let statusUpdateMessage = JSON.parse(statusUpdateMessageString); if ("event" in statusUpdateMessage) @@ -237,3 +248,10 @@ } } } + + +function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) { + console.log("updating web application (serialized message): ", statusUpdateMessageString); + console.log("<not supported!>"); +} + \ No newline at end of file
--- a/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegratedCpp_api.yaml Wed Apr 17 17:57:50 2019 +0200 +++ b/Resources/CodeGeneration/testWasmIntegrated/testWasmIntegratedCpp_api.yaml Thu Apr 18 09:30:00 2019 +0200 @@ -11,6 +11,7 @@ struct C: someBs: vector<B> ddd: vector<string> + definition: vector<json> struct A: someStrings: vector<string> @@ -30,6 +31,7 @@ titi: map<string, string> lulu: map<string, Message1> movieType: MovieType + definition: json enum MovieType: - RomCom @@ -40,10 +42,17 @@ enum CrispType: - SaltAndPepper - CreamAndChives + - Barbecue - Paprika - - Barbecue enum EnumMonth0: - January - February - March + +enum Tata: + - Lolo + - Rrrrrrrrrrrr + + +