Mercurial > hg > orthanc-stone
changeset 551:90f3a60576a9 dev rtviewer19
Merged in ct-pet-dose-struct (pull request #2)
Ct pet dose struct
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Tue, 02 Apr 2019 14:02:12 +0000 |
parents | e1ba16436d59 (current diff) 01c4b4583cc1 (diff) |
children | ac4ad0cc6fc2 636eb0e4c9dd |
files | |
diffstat | 21 files changed, 1371 insertions(+), 95 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Fri Mar 29 15:21:33 2019 +0100 +++ b/.hgignore Tue Apr 02 14:02:12 2019 +0000 @@ -3,6 +3,11 @@ 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/ @@ -20,3 +25,4 @@ Resources/CodeGeneration/testWasmIntegrated/build-wasm/ Resources/CodeGeneration/testWasmIntegrated/build-tsc/ Resources/CodeGeneration/testWasmIntegrated/build-final/ +
--- a/Applications/Samples/CMakeLists.txt Fri Mar 29 15:21:33 2019 +0100 +++ b/Applications/Samples/CMakeLists.txt Tue Apr 02 14:02:12 2019 +0000 @@ -12,6 +12,20 @@ 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")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/rt-viewer-demo/CMakeLists.txt Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -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 Tue Apr 02 14:02:12 2019 +0000 @@ -0,0 +1,11 @@ +{ + "extends" : "../../../Platforms/Wasm/tsconfig-stone.json", + "compilerOptions": { + "sourceMap": false, + "lib" : [ + "es2017", + "dom", + "dom.iterable" + ] + } +}
--- a/Framework/Layers/DicomSeriesVolumeSlicer.cpp Fri Mar 29 15:21:33 2019 +0100 +++ b/Framework/Layers/DicomSeriesVolumeSlicer.cpp Tue Apr 02 14:02:12 2019 +0000 @@ -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/Widgets/SliceViewerWidget.h Fri Mar 29 15:21:33 2019 +0100 +++ b/Framework/Widgets/SliceViewerWidget.h Tue Apr 02 14:02:12 2019 +0000 @@ -60,6 +60,9 @@ }; private: + SliceViewerWidget(const SliceViewerWidget&); + SliceViewerWidget& operator=(const SliceViewerWidget&); + class Scene; typedef std::map<const IVolumeSlicer*, size_t> LayersIndex;
--- a/Framework/dev.h Fri Mar 29 15:21:33 2019 +0100 +++ b/Framework/dev.h Tue Apr 02 14:02:12 2019 +0000 @@ -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 Fri Mar 29 15:21:33 2019 +0100 +++ b/Platforms/Generic/Oracle.cpp Tue Apr 02 14:02:12 2019 +0000 @@ -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/README.md Fri Mar 29 15:21:33 2019 +0100 +++ b/README.md Tue Apr 02 14:02:12 2019 +0000 @@ -137,15 +137,27 @@ ~/orthanc-stone/Applications/Samples/ ``` -**Ninja generator with static build (typical under Windows)** +**Ninja generator with static SDL build (pwsh script)** ``` # Please yourself one level above the orthanc-stone and orthanc folders -mkdir -p stone_build_sdl +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. @@ -160,18 +172,28 @@ Building the Qt native samples (SimpleViewer only) under Windows: ------------------------------------------------------------------ -**MSVC 2017 generator with static build (typical 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 -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 -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/`