Mercurial > hg > orthanc-stone
changeset 860:238693c3bc51 am-dev
merge default -> am-dev
author | Alain Mazy <alain@mazy.be> |
---|---|
date | Mon, 24 Jun 2019 14:35:00 +0200 |
parents | a6e17a5a39e7 (current diff) 6845a05f9526 (diff) |
children | 23701fbf228e |
files | .hgignore Applications/Qt/QCairoWidget.h Applications/Qt/QtStoneApplicationRunner.cpp Framework/Layers/CircleMeasureTracker.cpp Framework/Layers/CircleMeasureTracker.h Framework/Layers/ColorFrameRenderer.cpp Framework/Layers/ColorFrameRenderer.h Framework/Layers/DicomSeriesVolumeSlicer.cpp Framework/Layers/DicomSeriesVolumeSlicer.h Framework/Layers/DicomStructureSetSlicer.cpp Framework/Layers/DicomStructureSetSlicer.h Framework/Layers/FrameRenderer.cpp Framework/Layers/FrameRenderer.h Framework/Layers/GrayscaleFrameRenderer.cpp Framework/Layers/GrayscaleFrameRenderer.h Framework/Layers/ILayerRenderer.h Framework/Layers/IVolumeSlicer.h Framework/Layers/LineLayerRenderer.cpp Framework/Layers/LineLayerRenderer.h Framework/Layers/LineMeasureTracker.cpp Framework/Layers/LineMeasureTracker.h Framework/Layers/RenderStyle.cpp Framework/Layers/RenderStyle.h Framework/Layers/SeriesFrameRendererFactory.cpp Framework/Layers/SeriesFrameRendererFactory.h Framework/Layers/SingleFrameRendererFactory.cpp Framework/Layers/SingleFrameRendererFactory.h Framework/Layers/SliceOutlineRenderer.cpp Framework/Layers/SliceOutlineRenderer.h Framework/Radiography/RadiographySceneReader.cpp Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Framework/Scene2DViewport/EditAngleMeasureTracker.cpp Framework/Scene2DViewport/EditAngleMeasureTracker.h Framework/Scene2DViewport/EditCircleMeasureTracker.cpp Framework/Scene2DViewport/EditCircleMeasureTracker.h Framework/Scene2DViewport/EditLineMeasureTracker.cpp Framework/Scene2DViewport/EditLineMeasureTracker.h Framework/Scene2DViewport/MeasureTools.cpp Framework/Scene2DViewport/MeasureTools.h Framework/Scene2DViewport/PointerTypes.h Framework/SmartLoader.cpp Framework/SmartLoader.h Framework/Toolbox/BaseWebService.cpp Framework/Toolbox/BaseWebService.h Framework/Toolbox/DicomFrameConverter.cpp Framework/Toolbox/DicomFrameConverter.h Framework/Toolbox/DownloadStack.cpp Framework/Toolbox/DownloadStack.h Framework/Toolbox/IDelayedCallExecutor.h Framework/Toolbox/ISeriesLoader.h Framework/Toolbox/IWebService.cpp Framework/Toolbox/IWebService.h Framework/Toolbox/MessagingToolbox.cpp Framework/Toolbox/MessagingToolbox.h Framework/Toolbox/OrientedBoundingBox.cpp Framework/Toolbox/OrientedBoundingBox.h Framework/Toolbox/OrthancApiClient.cpp Framework/Toolbox/OrthancApiClient.h Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.h Framework/Toolbox/ParallelSlices.cpp Framework/Toolbox/ParallelSlices.h Framework/Toolbox/ParallelSlicesCursor.cpp Framework/Toolbox/ParallelSlicesCursor.h Framework/Toolbox/Slice.cpp Framework/Toolbox/Slice.h Framework/Toolbox/ViewportGeometry.cpp Framework/Toolbox/ViewportGeometry.h Framework/Toolbox/VolumeImageGeometry.cpp Framework/Toolbox/VolumeImageGeometry.h Framework/Viewport/CairoContext.cpp Framework/Viewport/CairoContext.h Framework/Viewport/CairoFont.cpp Framework/Viewport/CairoFont.h Framework/Viewport/CairoSurface.cpp Framework/Viewport/CairoSurface.h Framework/Viewport/IMouseTracker.h Framework/Viewport/IStatusBar.h Framework/Viewport/IViewport.h Framework/Viewport/WidgetViewport.cpp Framework/Viewport/WidgetViewport.h Framework/Volumes/ISlicedVolume.h Framework/Volumes/IVolumeLoader.h Framework/Volumes/StructureSetLoader.cpp Framework/Volumes/StructureSetLoader.h Framework/Widgets/CairoWidget.cpp Framework/Widgets/CairoWidget.h Framework/Widgets/EmptyWidget.cpp Framework/Widgets/EmptyWidget.h Framework/Widgets/IWidget.h Framework/Widgets/IWorldSceneInteractor.h Framework/Widgets/IWorldSceneMouseTracker.h Framework/Widgets/LayoutWidget.cpp Framework/Widgets/LayoutWidget.h Framework/Widgets/PanMouseTracker.cpp Framework/Widgets/PanMouseTracker.h Framework/Widgets/PanZoomMouseTracker.cpp Framework/Widgets/PanZoomMouseTracker.h Framework/Widgets/SliceViewerWidget.cpp Framework/Widgets/SliceViewerWidget.h Framework/Widgets/TestCairoWidget.cpp Framework/Widgets/TestCairoWidget.h Framework/Widgets/TestWorldSceneWidget.cpp Framework/Widgets/TestWorldSceneWidget.h Framework/Widgets/WidgetBase.cpp Framework/Widgets/WidgetBase.h Framework/Widgets/WorldSceneWidget.cpp Framework/Widgets/WorldSceneWidget.h Framework/Widgets/ZoomMouseTracker.cpp Framework/Widgets/ZoomMouseTracker.h Framework/dev.h Platforms/Wasm/Defaults.cpp Resources/CMake/OrthancStoneConfiguration.cmake |
diffstat | 408 files changed, 27086 insertions(+), 18712 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Wed Jun 19 17:36:33 2019 +0200 +++ b/.hgignore Mon Jun 24 14:35:00 2019 +0200 @@ -35,3 +35,6 @@ Samples/Sdl/CMakeLists.txt.orig Samples/Qt/ThirdPartyDownloads/ +Samples/WebAssembly/build/ +Samples/WebAssembly/ThirdPartyDownloads/ +Samples/WebAssembly/installDir/
--- a/.hgtags Wed Jun 19 17:36:33 2019 +0200 +++ b/.hgtags Mon Jun 24 14:35:00 2019 +0200 @@ -1,1 +1,2 @@ 90f3a60576a9f08dcf783752a7f67ce0615a5371 rtviewer19 +6d15261f9c9965a2f4b64658e318b370933b175e toa2019061901
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Generic/GuiAdapter.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,799 @@ +/** + * 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 "GuiAdapter.h" + +#if ORTHANC_ENABLE_OPENGL == 1 +# include "../../Framework/OpenGL/OpenGLIncludes.h" +#endif + +#if ORTHANC_ENABLE_SDL == 1 +# include <SDL_video.h> +# include <SDL_render.h> +# include <SDL.h> +#endif + +#if ORTHANC_ENABLE_THREADS == 1 +# include "../../Framework/Messages/LockingEmitter.h" +#endif + +namespace OrthancStone +{ + void GuiAdapter::RegisterWidget(boost::shared_ptr<IGuiAdapterWidget> widget) + { + widgets_.push_back(widget); + } + + std::ostream& operator<<( + std::ostream& os, const GuiAdapterKeyboardEvent& event) + { + os << "ctrl: " << event.ctrlKey << ", " << + "shift: " << event.shiftKey << ", " << + "alt: " << event.altKey; + return os; + } + +#if ORTHANC_ENABLE_WASM == 1 + void GuiAdapter::Run() + { + } + + void ConvertFromPlatform( + GuiAdapterUiEvent& dest, + int eventType, + const EmscriptenUiEvent& src) + { + // no data for now + } + + void ConvertFromPlatform( + GuiAdapterMouseEvent& dest, + int eventType, + const EmscriptenMouseEvent& src) + { + memset(&dest, 0, sizeof(GuiAdapterMouseEvent)); + switch (eventType) + { + case EMSCRIPTEN_EVENT_CLICK: + LOG(ERROR) << "Emscripten EMSCRIPTEN_EVENT_CLICK is not supported"; + ORTHANC_ASSERT(false, "Not supported"); + break; + case EMSCRIPTEN_EVENT_MOUSEDOWN: + dest.type = GUIADAPTER_EVENT_MOUSEDOWN; + break; + case EMSCRIPTEN_EVENT_MOUSEMOVE: + dest.type = GUIADAPTER_EVENT_MOUSEMOVE; + break; + case EMSCRIPTEN_EVENT_MOUSEUP: + dest.type = GUIADAPTER_EVENT_MOUSEUP; + break; + case EMSCRIPTEN_EVENT_WHEEL: + dest.type = GUIADAPTER_EVENT_WHEEL; + break; + + default: + LOG(ERROR) << "Emscripten event: " << eventType << " is not supported"; + ORTHANC_ASSERT(false, "Not supported"); + } + //dest.timestamp = src.timestamp; + //dest.screenX = src.screenX; + //dest.screenY = src.screenY; + //dest.clientX = src.clientX; + //dest.clientY = src.clientY; + dest.ctrlKey = src.ctrlKey; + dest.shiftKey = src.shiftKey; + dest.altKey = src.altKey; + //dest.metaKey = src.metaKey; + dest.button = src.button; + //dest.buttons = src.buttons; + //dest.movementX = src.movementX; + //dest.movementY = src.movementY; + dest.targetX = src.targetX; + dest.targetY = src.targetY; + //dest.canvasX = src.canvasX; + //dest.canvasY = src.canvasY; + //dest.padding = src.padding; + } + + void ConvertFromPlatform( + GuiAdapterWheelEvent& dest, + int eventType, + const EmscriptenWheelEvent& src) + { + ConvertFromPlatform(dest.mouse, eventType, src.mouse); + dest.deltaX = src.deltaX; + dest.deltaY = src.deltaY; + switch (src.deltaMode) + { + case DOM_DELTA_PIXEL: + dest.deltaMode = GUIADAPTER_DELTA_PIXEL; + break; + case DOM_DELTA_LINE: + dest.deltaMode = GUIADAPTER_DELTA_LINE; + break; + case DOM_DELTA_PAGE: + dest.deltaMode = GUIADAPTER_DELTA_PAGE; + break; + default: + ORTHANC_ASSERT(false, "Unknown deltaMode: " << src.deltaMode << + " in wheel event..."); + } + dest.deltaMode = src.deltaMode; + } + + void ConvertFromPlatform( + GuiAdapterKeyboardEvent& dest, + const EmscriptenKeyboardEvent& src) + { + dest.ctrlKey = src.ctrlKey; + dest.shiftKey = src.shiftKey; + dest.altKey = src.altKey; + } + + template<typename GenericFunc> + struct FuncAdapterPayload + { + std::string canvasId; + void* userData; + GenericFunc callback; + }; + + template<typename GenericFunc, + typename GuiAdapterEvent, + typename EmscriptenEvent> + EM_BOOL OnEventAdapterFunc( + int eventType, const EmscriptenEvent* emEvent, void* userData) + { + + // userData is OnMouseWheelFuncAdapterPayload + FuncAdapterPayload<GenericFunc>* payload = + reinterpret_cast<FuncAdapterPayload<GenericFunc>*>(userData); + // LOG(INFO) << "OnEventAdapterFunc"; + // LOG(INFO) << "------------------"; + // LOG(INFO) << "eventType: " << eventType << " wheelEvent: " << + // (int)wheelEvent << " userData: " << userData << + // " payload->userData: " << payload->userData; + + GuiAdapterEvent guiEvent; + ConvertFromPlatform(guiEvent, eventType, *emEvent); + bool ret = (*(payload->callback))(payload->canvasId, &guiEvent, payload->userData); + return static_cast<EM_BOOL>(ret); + } + + template<typename GenericFunc, + typename GuiAdapterEvent, + typename EmscriptenEvent> + EM_BOOL OnEventAdapterFunc2( + int /*eventType*/, const EmscriptenEvent* wheelEvent, void* userData) + { + // userData is OnMouseWheelFuncAdapterPayload + FuncAdapterPayload<GenericFunc>* payload = + reinterpret_cast<FuncAdapterPayload<GenericFunc>*>(userData); + + GuiAdapterEvent guiEvent; + ConvertFromPlatform(guiEvent, *wheelEvent); + bool ret = (*(payload->callback))(payload->canvasId, &guiEvent, payload->userData); + return static_cast<EM_BOOL>(ret); + } + + template<typename GenericFunc> + EM_BOOL OnEventAdapterFunc3( + double time, void* userData) + { + // userData is OnMouseWheelFuncAdapterPayload + FuncAdapterPayload<GenericFunc>* payload = + reinterpret_cast<FuncAdapterPayload<GenericFunc>*>(userData); + //std::auto_ptr< FuncAdapterPayload<GenericFunc> > deleter(payload); + bool ret = (*(payload->callback))(time, payload->userData); + return static_cast<EM_BOOL>(ret); + } + + // resize: (const char* target, void* userData, EM_BOOL useCapture, em_ui_callback_func callback) + template< + typename GenericFunc, + typename GuiAdapterEvent, + typename EmscriptenEvent, + typename EmscriptenSetCallbackFunc> + static void SetCallback( + EmscriptenSetCallbackFunc emFunc, + std::string canvasId, void* userData, bool capture, GenericFunc func) + { + // TODO: write RemoveCallback with an int id that gets returned from + // here + FuncAdapterPayload<GenericFunc>* payload = + new FuncAdapterPayload<GenericFunc>(); + std::auto_ptr<FuncAdapterPayload<GenericFunc> > payloadP(payload); + payload->canvasId = canvasId; + payload->callback = func; + payload->userData = userData; + void* userDataRaw = reinterpret_cast<void*>(payload); + // LOG(INFO) << "SetCallback -- userDataRaw: " << userDataRaw << + // " payload: " << payload << " payload->userData: " << payload->userData; + (*emFunc)( + canvasId.c_str(), + userDataRaw, + static_cast<EM_BOOL>(capture), + &OnEventAdapterFunc<GenericFunc, GuiAdapterEvent, EmscriptenEvent>, + EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD); + payloadP.release(); + } + + template< + typename GenericFunc, + typename GuiAdapterEvent, + typename EmscriptenEvent, + typename EmscriptenSetCallbackFunc> + static void SetCallback2( + EmscriptenSetCallbackFunc emFunc, + std::string canvasId, void* userData, bool capture, GenericFunc func) + { + std::auto_ptr<FuncAdapterPayload<GenericFunc> > payload( + new FuncAdapterPayload<GenericFunc>() + ); + payload->canvasId = canvasId; + payload->callback = func; + payload->userData = userData; + void* userDataRaw = reinterpret_cast<void*>(payload.release()); + (*emFunc)( + canvasId.c_str(), + userDataRaw, + static_cast<EM_BOOL>(capture), + &OnEventAdapterFunc2<GenericFunc, GuiAdapterEvent, EmscriptenEvent>, + EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD); + } + + template< + typename GenericFunc, + typename EmscriptenSetCallbackFunc> + static void SetAnimationFrameCallback( + EmscriptenSetCallbackFunc emFunc, + void* userData, GenericFunc func) + { + // LOG(ERROR) << "SetAnimationFrameCallback !!!!!! (RequestAnimationFrame)"; + std::auto_ptr<FuncAdapterPayload<GenericFunc> > payload( + new FuncAdapterPayload<GenericFunc>() + ); + payload->canvasId = "UNDEFINED"; + payload->callback = func; + payload->userData = userData; + void* userDataRaw = reinterpret_cast<void*>(payload.release()); + (*emFunc)( + &OnEventAdapterFunc3<GenericFunc>, + userDataRaw); + } + + void GuiAdapter::SetWheelCallback( + std::string canvasId, void* userData, bool capture, OnMouseWheelFunc func) + { + SetCallback<OnMouseWheelFunc, GuiAdapterWheelEvent, EmscriptenWheelEvent>( + &emscripten_set_wheel_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetMouseDownCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + SetCallback<OnMouseEventFunc, GuiAdapterMouseEvent, EmscriptenMouseEvent>( + &emscripten_set_mousedown_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetMouseMoveCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + // LOG(INFO) << "SetMouseMoveCallback -- " << "supplied userData: " << + // userData; + + SetCallback<OnMouseEventFunc, GuiAdapterMouseEvent, EmscriptenMouseEvent>( + &emscripten_set_mousemove_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetMouseUpCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + SetCallback<OnMouseEventFunc, GuiAdapterMouseEvent, EmscriptenMouseEvent>( + &emscripten_set_mouseup_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetKeyDownCallback( + std::string canvasId, void* userData, bool capture, OnKeyDownFunc func) + { + SetCallback2<OnKeyDownFunc, GuiAdapterKeyboardEvent, EmscriptenKeyboardEvent>( + &emscripten_set_keydown_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetKeyUpCallback( + std::string canvasId, void* userData, bool capture, OnKeyUpFunc func) + { + SetCallback2<OnKeyUpFunc, GuiAdapterKeyboardEvent, EmscriptenKeyboardEvent>( + &emscripten_set_keyup_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::SetResizeCallback( + std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func) + { + SetCallback<OnWindowResizeFunc, GuiAdapterUiEvent, EmscriptenUiEvent>( + &emscripten_set_resize_callback_on_thread, + canvasId, + userData, + capture, + func); + } + + void GuiAdapter::RequestAnimationFrame( + OnAnimationFrameFunc func, void* userData) + { + // LOG(ERROR) << "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"; + // LOG(ERROR) << "RequestAnimationFrame"; + // LOG(ERROR) << "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"; + SetAnimationFrameCallback<OnAnimationFrameFunc>( + &emscripten_request_animation_frame_loop, + userData, + func); + } + +#if 0 + void GuiAdapter::SetKeyDownCallback( + std::string canvasId, void* userData, bool capture, OnKeyDownFunc func) + { + emscripten_set_keydown_callback(canvasId.c_str(), userData, static_cast<EM_BOOL>(capture), func); + } + void GuiAdapter::SetKeyUpCallback( + std::string canvasId, void* userData, bool capture, OnKeyUpFunc func) + { + emscripten_set_keyup_callback(canvasId.c_str(), userData, static_cast<EM_BOOL>(capture), func); + } + + void GuiAdapter::SetResizeCallback(std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func) + { + emscripten_set_resize_callback(canvasId.c_str(), userData, static_cast<EM_BOOL>(capture), func); + } + + void GuiAdapter::RequestAnimationFrame(OnAnimationFrameFunc func, void* userData) + { + emscripten_request_animation_frame_loop(func, userData); + } +#endif + + +#else + +// SDL ONLY +void ConvertFromPlatform( + GuiAdapterMouseEvent& dest, + bool ctrlPressed, bool shiftPressed, bool altPressed, + const SDL_Event& source) +{ + memset(&dest, 0, sizeof(GuiAdapterMouseEvent)); + switch (source.type) + { + case SDL_MOUSEBUTTONDOWN: + dest.type = GUIADAPTER_EVENT_MOUSEDOWN; + break; + case SDL_MOUSEMOTION: + dest.type = GUIADAPTER_EVENT_MOUSEMOVE; + break; + case SDL_MOUSEBUTTONUP: + dest.type = GUIADAPTER_EVENT_MOUSEUP; + break; + case SDL_MOUSEWHEEL: + dest.type = GUIADAPTER_EVENT_WHEEL; + break; + default: + LOG(ERROR) << "SDL event: " << source.type << " is not supported"; + ORTHANC_ASSERT(false, "Not supported"); + } + //dest.timestamp = src.timestamp; + //dest.screenX = src.screenX; + //dest.screenY = src.screenY; + //dest.clientX = src.clientX; + //dest.clientY = src.clientY; + dest.ctrlKey = ctrlPressed; + dest.shiftKey = shiftPressed; + dest.altKey = altPressed; + //dest.metaKey = src.metaKey; + switch (source.button.button) + { + case SDL_BUTTON_MIDDLE: + dest.button = 1; + break; + + case SDL_BUTTON_RIGHT: + dest.button = 2; + break; + + case SDL_BUTTON_LEFT: + dest.button = 0; + break; + + default: + break; + } + //dest.buttons = src.buttons; + //dest.movementX = src.movementX; + //dest.movementY = src.movementY; + dest.targetX = source.button.x; + dest.targetY = source.button.y; + //dest.canvasX = src.canvasX; + //dest.canvasY = src.canvasY; + //dest.padding = src.padding; + } + +void ConvertFromPlatform( + GuiAdapterWheelEvent& dest, + bool ctrlPressed, bool shiftPressed, bool altPressed, + const SDL_Event& source) +{ + ConvertFromPlatform(dest.mouse, ctrlPressed, shiftPressed, altPressed, source); + dest.deltaX = source.wheel.x; + dest.deltaY = source.wheel.y; +} + + + + // SDL ONLY + void GuiAdapter::SetResizeCallback( + std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func) + { + resizeHandlers_.push_back(EventHandlerData<OnWindowResizeFunc>(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetMouseDownCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + mouseDownHandlers_.push_back(EventHandlerData<OnMouseEventFunc>(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetMouseMoveCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + mouseMoveHandlers_.push_back(EventHandlerData<OnMouseEventFunc>(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetMouseUpCallback( + std::string canvasId, void* userData, bool capture, OnMouseEventFunc func) + { + mouseUpHandlers_.push_back(EventHandlerData<OnMouseEventFunc>(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetWheelCallback( + std::string canvasId, void* userData, bool capture, OnMouseWheelFunc func) + { + mouseWheelHandlers_.push_back(EventHandlerData<OnMouseWheelFunc>(canvasId, func, userData)); + } + + // SDL ONLY + void GuiAdapter::SetKeyDownCallback( + std::string canvasId, void* userData, bool capture, OnKeyDownFunc func) + { + } + + // SDL ONLY + void GuiAdapter::SetKeyUpCallback( + std::string canvasId, void* userData, bool capture, OnKeyUpFunc func) + { + } + + + // SDL ONLY + void GuiAdapter::OnAnimationFrame() + { + for (size_t i = 0; i < animationFrameHandlers_.size(); i++) + { + // TODO: fix time + (*(animationFrameHandlers_[i].first))(0, animationFrameHandlers_[i].second); + } + } + + // SDL ONLY + void GuiAdapter::OnResize() + { + for (size_t i = 0; i < resizeHandlers_.size(); i++) + { + (*(resizeHandlers_[i].func))( + resizeHandlers_[i].canvasName, 0, resizeHandlers_[i].userData); + } + } + + // SDL ONLY + void GuiAdapter::OnMouseWheelEvent(uint32_t windowID, const GuiAdapterWheelEvent& event) + { + + // the SDL window name IS the canvas name ("canvas" is used because this lib + // is designed for Wasm + SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); + ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); + + const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow); + ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); + + std::string windowTitle(windowTitleSz); + ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); + + switch (event.mouse.type) + { + case GUIADAPTER_EVENT_WHEEL: + for (size_t i = 0; i < mouseWheelHandlers_.size(); i++) + { + if(mouseWheelHandlers_[i].canvasName == windowTitle) + (*(mouseWheelHandlers_[i].func))(windowTitle, &event, mouseWheelHandlers_[i].userData); + } + break; + default: + ORTHANC_ASSERT(false, "Wrong event.type: " << event.mouse.type << " in GuiAdapter::OnMouseWheelEvent(...)"); + break; + } + } + + // SDL ONLY + void GuiAdapter::OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event) + { + // the SDL window name IS the canvas name ("canvas" is used because this lib + // is designed for Wasm + SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID); + ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << windowID << "\" is not a valid SDL window ID!"); + + const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow); + ORTHANC_ASSERT(windowTitleSz != NULL, "Window ID \"" << windowID << "\" has a NULL window title!"); + + std::string windowTitle(windowTitleSz); + ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowID << "\" has an empty window title!"); + + switch (event.type) + { + case GUIADAPTER_EVENT_MOUSEDOWN: + for (size_t i = 0; i < mouseDownHandlers_.size(); i++) + { + if (mouseDownHandlers_[i].canvasName == windowTitle) + (*(mouseDownHandlers_[i].func))(windowTitle, &event, mouseDownHandlers_[i].userData); + } + break; + case GUIADAPTER_EVENT_MOUSEMOVE: + for (size_t i = 0; i < mouseMoveHandlers_.size(); i++) + { + if (mouseMoveHandlers_[i].canvasName == windowTitle) + (*(mouseMoveHandlers_[i].func))(windowTitle, &event, mouseMoveHandlers_[i].userData); + } + break; + case GUIADAPTER_EVENT_MOUSEUP: + for (size_t i = 0; i < mouseUpHandlers_.size(); i++) + { + if (mouseUpHandlers_[i].canvasName == windowTitle) + (*(mouseUpHandlers_[i].func))(windowTitle, &event, mouseUpHandlers_[i].userData); + } + break; + default: + ORTHANC_ASSERT(false, "Wrong event.type: " << event.type << " in GuiAdapter::OnMouseEvent(...)"); + break; + } + + ////boost::shared_ptr<IGuiAdapterWidget> GetWidgetFromWindowId(); + //boost::shared_ptr<IGuiAdapterWidget> foundWidget; + //VisitWidgets([foundWidget, windowID](auto widget) + // { + // if (widget->GetSdlWindowID() == windowID) + // foundWidget = widget; + // }); + //ORTHANC_ASSERT(foundWidget, "WindowID " << windowID << " was not found in the registered widgets!"); + //if(foundWidget) + // foundWidget-> + } + + // SDL ONLY + void GuiAdapter::RequestAnimationFrame(OnAnimationFrameFunc func, void* userData) + { + animationFrameHandlers_.push_back(std::make_pair(func, userData)); + } + +# if ORTHANC_ENABLE_OPENGL == 1 && !defined(__APPLE__) /* OpenGL debug is not available on OS X */ + + // SDL ONLY + static void GLAPIENTRY + OpenGLMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) + { + if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) + { + fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), + type, severity, message); + } +} +# endif + + // SDL ONLY + void GuiAdapter::Run() + { +# if ORTHANC_ENABLE_OPENGL == 1 && !defined(__APPLE__) + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); +# endif + + // Uint32 SDL_GetWindowID(SDL_Window* window) + // SDL_Window* SDL_GetWindowFromID(Uint32 id) // may return NULL + + bool stop = false; + while (!stop) + { + { + LockingEmitter::WriterLock lock(lockingEmitter_); + OnAnimationFrame(); // in SDL we must call it + } + + SDL_Event event; + + while (!stop && SDL_PollEvent(&event)) + { + LockingEmitter::WriterLock lock(lockingEmitter_); + + if (event.type == SDL_QUIT) + { + // TODO: call exit callbacks here + stop = true; + break; + } + else if ( (event.type == SDL_MOUSEMOTION) || + (event.type == SDL_MOUSEBUTTONDOWN) || + (event.type == SDL_MOUSEBUTTONUP) ) + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + bool ctrlPressed(false); + bool shiftPressed(false); + bool altPressed(false); + + if (SDL_SCANCODE_LCTRL < scancodeCount && keyboardState[SDL_SCANCODE_LCTRL]) + ctrlPressed = true; + if (SDL_SCANCODE_RCTRL < scancodeCount && keyboardState[SDL_SCANCODE_RCTRL]) + ctrlPressed = true; + if (SDL_SCANCODE_LSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_LSHIFT]) + shiftPressed = true; + if (SDL_SCANCODE_RSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_RSHIFT]) + shiftPressed = true; + if (SDL_SCANCODE_LALT < scancodeCount && keyboardState[SDL_SCANCODE_LALT]) + altPressed = true; + + GuiAdapterMouseEvent dest; + ConvertFromPlatform(dest, ctrlPressed, shiftPressed, altPressed, event); + OnMouseEvent(event.window.windowID, dest); +#if 0 + // for reference, how to create trackers + if (tracker) + { + PointerEvent e; + e.AddPosition(compositor.GetPixelCenterCoordinates( + event.button.x, event.button.y)); + tracker->PointerMove(e); + } +#endif + } + else if (event.type == SDL_MOUSEWHEEL) + { + + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + bool ctrlPressed(false); + bool shiftPressed(false); + bool altPressed(false); + + if (SDL_SCANCODE_LCTRL < scancodeCount && keyboardState[SDL_SCANCODE_LCTRL]) + ctrlPressed = true; + if (SDL_SCANCODE_RCTRL < scancodeCount && keyboardState[SDL_SCANCODE_RCTRL]) + ctrlPressed = true; + if (SDL_SCANCODE_LSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_LSHIFT]) + shiftPressed = true; + if (SDL_SCANCODE_RSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_RSHIFT]) + shiftPressed = true; + if (SDL_SCANCODE_LALT < scancodeCount && keyboardState[SDL_SCANCODE_LALT]) + altPressed = true; + + GuiAdapterWheelEvent dest; + ConvertFromPlatform(dest, ctrlPressed, shiftPressed, altPressed, event); + OnMouseWheelEvent(event.window.windowID, dest); + + //KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); + + //int x, y; + //SDL_GetMouseState(&x, &y); + + //if (event.wheel.y > 0) + //{ + // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers); + //} + //else if (event.wheel.y < 0) + //{ + // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers); + //} + } + else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { +#if 0 + tracker.reset(); +#endif + OnResize(); + } + else if (event.type == SDL_KEYDOWN && event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + // window.GetWindow().ToggleMaximize(); //TODO: move to particular handler + break; + + case SDLK_s: +#if 0 + // TODO: re-enable at application-level!!!! + VisitWidgets( + [](auto value) + { + auto widget = boost::dynamic_pointer_cast<VolumeSlicerWidget() + value->FitContent(); + }); +#endif + break; + + case SDLK_q: + stop = true; + break; + + default: + break; + } + } + // HandleApplicationEvent(controller, compositor, event, tracker); + } + + SDL_Delay(1); + } + } +#endif +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Generic/GuiAdapter.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,363 @@ +/** + * 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 <string> + +#if ORTHANC_ENABLE_WASM != 1 +# ifdef __EMSCRIPTEN__ +# error __EMSCRIPTEN__ is defined and ORTHANC_ENABLE_WASM != 1 +# endif +#endif + +#if ORTHANC_ENABLE_WASM == 1 +# ifndef __EMSCRIPTEN__ +# error __EMSCRIPTEN__ is not defined and ORTHANC_ENABLE_WASM == 1 +# endif +#endif + +#if ORTHANC_ENABLE_WASM == 1 +# include <emscripten/html5.h> +#else +# if ORTHANC_ENABLE_SDL == 1 +# include <SDL.h> +# endif +#endif + +#include "../../Framework/StoneException.h" + +#if ORTHANC_ENABLE_THREADS != 1 +# include "../../Framework/Messages/LockingEmitter.h" +#endif + +#include <vector> +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> + +namespace OrthancStone +{ + + /** + This interface is used to store the widgets that are controlled by the + GuiAdapter and receive event callbacks. + The callbacks may possibly be downcast (using dynamic_cast, for safety) \ + to the actual widget type + */ + class IGuiAdapterWidget + { + public: + virtual ~IGuiAdapterWidget() {} + + }; + + enum GuiAdapterMouseEventType + { + GUIADAPTER_EVENT_MOUSEDOWN = 1973, + GUIADAPTER_EVENT_MOUSEMOVE = 1974, + GUIADAPTER_EVENT_MOUSEUP = 1975, + GUIADAPTER_EVENT_WHEEL = 1976 + }; + + const unsigned int GUIADAPTER_DELTA_PIXEL = 2973; + const unsigned int GUIADAPTER_DELTA_LINE = 2974; + const unsigned int GUIADAPTER_DELTA_PAGE = 2975; + + struct GuiAdapterUiEvent; + struct GuiAdapterMouseEvent; + struct GuiAdapterWheelEvent; + struct GuiAdapterKeyboardEvent; + + class LockingEmitter; + +#if 1 + typedef bool (*OnMouseEventFunc)(std::string canvasId, const GuiAdapterMouseEvent* mouseEvent, void* userData); + typedef bool (*OnMouseWheelFunc)(std::string canvasId, const GuiAdapterWheelEvent* wheelEvent, void* userData); + typedef bool (*OnKeyDownFunc) (std::string canvasId, const GuiAdapterKeyboardEvent* keyEvent, void* userData); + typedef bool (*OnKeyUpFunc) (std::string canvasId, const GuiAdapterKeyboardEvent* keyEvent, void* userData); + + typedef bool (*OnAnimationFrameFunc)(double time, void* userData); + typedef bool (*OnWindowResizeFunc)(std::string canvasId, const GuiAdapterUiEvent* uiEvent, void* userData); + +#else + +#if ORTHANC_ENABLE_WASM == 1 + typedef EM_BOOL (*OnMouseEventFunc)(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); + typedef EM_BOOL (*OnMouseWheelFunc)(int eventType, const EmscriptenWheelEvent* wheelEvent, void* userData); + typedef EM_BOOL (*OnKeyDownFunc) (int eventType, const EmscriptenKeyboardEvent* keyEvent, void* userData); + typedef EM_BOOL (*OnKeyUpFunc) (int eventType, const EmscriptenKeyboardEvent* keyEvent, void* userData); + + typedef EM_BOOL (*OnAnimationFrameFunc)(double time, void* userData); + typedef EM_BOOL (*OnWindowResizeFunc)(int eventType, const EmscriptenUiEvent* uiEvent, void* userData); +#else + typedef bool (*OnMouseEventFunc)(int eventType, const SDL_Event* mouseEvent, void* userData); + typedef bool (*OnMouseWheelFunc)(int eventType, const SDL_Event* wheelEvent, void* userData); + typedef bool (*OnKeyDownFunc) (int eventType, const SDL_Event* keyEvent, void* userData); + typedef bool (*OnKeyUpFunc) (int eventType, const SDL_Event* keyEvent, void* userData); + + typedef bool (*OnAnimationFrameFunc)(double time, void* userData); + typedef bool (*OnWindowResizeFunc)(int eventType, const GuiAdapterUiEvent* uiEvent, void* userData); +#endif + +#endif + struct GuiAdapterMouseEvent { + GuiAdapterMouseEventType type; + //double timestamp; + //long screenX; + //long screenY; + //long clientX; + //long clientY; + bool ctrlKey; + bool shiftKey; + bool altKey; + //bool metaKey; + unsigned short button; + //unsigned short buttons; + //long movementX; + //long movementY; + long targetX; + long targetY; + // canvasX and canvasY are deprecated - there no longer exists a Module['canvas'] object, so canvasX/Y are no longer reported (register a listener on canvas directly to get canvas coordinates, or translate manually) + //long canvasX; + //long canvasY; + //long padding; + }; + + struct GuiAdapterWheelEvent { + GuiAdapterMouseEvent mouse; + double deltaX; + double deltaY; + unsigned long deltaMode; + }; + + // we don't use any data now + struct GuiAdapterUiEvent {}; + + // EmscriptenKeyboardEvent + struct GuiAdapterKeyboardEvent + { + bool ctrlKey; + bool shiftKey; + bool altKey; + }; + + std::ostream& operator<<(std::ostream& os, const GuiAdapterKeyboardEvent& event); + + /* + Mousedown event trigger when either the left or right (or middle) mouse is pressed + on the object; + + Mouseup event trigger when either the left or right (or middle) mouse is released + above the object after triggered mousedown event and held. + + Click event trigger when the only left mouse button is pressed and released on the + same object, requires the Mousedown and Mouseup event happened before Click event. + + The normal expect trigger order: onMousedown >> onMouseup >> onClick + + Testing in Chrome v58, the time between onMouseup and onClick events are around + 7ms to 15ms + + FROM: https://codingrepo.com/javascript/2017/05/19/javascript-difference-mousedown-mouseup-click-events/ + */ +#if ORTHANC_ENABLE_WASM == 1 + void ConvertFromPlatform( + GuiAdapterUiEvent& dest, + int eventType, + const EmscriptenUiEvent& src); + + void ConvertFromPlatform( + GuiAdapterMouseEvent& dest, + int eventType, + const EmscriptenMouseEvent& src); + + void ConvertFromPlatform( + GuiAdapterWheelEvent& dest, + int eventType, + const EmscriptenWheelEvent& src); + + void ConvertFromPlatform( + GuiAdapterKeyboardEvent& dest, + const EmscriptenKeyboardEvent& src); + +#else + +# if ORTHANC_ENABLE_SDL == 1 + void ConvertFromPlatform( + GuiAdapterMouseEvent& dest, + bool ctrlPressed, bool shiftPressed, bool altPressed, + const SDL_Event& source); + + void ConvertFromPlatform( + GuiAdapterWheelEvent& dest, + bool ctrlPressed, bool shiftPressed, bool altPressed, + const SDL_Event& source); + +# endif + +#endif + + class GuiAdapter + { + public: +#if ORTHANC_ENABLE_THREADS == 1 + GuiAdapter(LockingEmitter& lockingEmitter) : lockingEmitter_(lockingEmitter) +#else + GuiAdapter() +#endif + { + static int instanceCount = 0; + ORTHANC_ASSERT(instanceCount == 0); + instanceCount = 1; + } + + void RegisterWidget(boost::shared_ptr<IGuiAdapterWidget> widget); + + /** + emscripten_set_resize_callback("#window", NULL, false, OnWindowResize); + + emscripten_set_wheel_callback("mycanvas1", widget1_.get(), false, OnXXXMouseWheel); + emscripten_set_wheel_callback("mycanvas2", widget2_.get(), false, OnXXXMouseWheel); + emscripten_set_wheel_callback("mycanvas3", widget3_.get(), false, OnXXXMouseWheel); + + emscripten_set_keydown_callback("#window", NULL, false, OnKeyDown); + emscripten_set_keyup_callback("#window", NULL, false, OnKeyUp); + + emscripten_request_animation_frame_loop(OnAnimationFrame, NULL); + + SDL: + see https://wiki.libsdl.org/SDL_CaptureMouse + + */ + + void SetMouseDownCallback(std::string canvasId, void* userData, bool capture, OnMouseEventFunc func); + void SetMouseMoveCallback(std::string canvasId, void* userData, bool capture, OnMouseEventFunc func); + void SetMouseUpCallback (std::string canvasId, void* userData, bool capture, OnMouseEventFunc func); + void SetWheelCallback (std::string canvasId, void* userData, bool capture, OnMouseWheelFunc func); + void SetKeyDownCallback (std::string canvasId, void* userData, bool capture, OnKeyDownFunc func); + void SetKeyUpCallback (std::string canvasId, void* userData, bool capture, OnKeyUpFunc func); + + // if you pass "#window", under SDL, then any Window resize will trigger the callback + void SetResizeCallback (std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func); + + void RequestAnimationFrame(OnAnimationFrameFunc func, void* userData); + + // TODO: implement and call to remove canvases [in SDL, although code should be generic] + void SetOnExitCallback(); + + // void + // OnWindowResize + // oracle + // broker + + /** + Under SDL, this function does NOT return until all windows have been closed. + Under wasm, it returns without doing anything, since the event loop is managed + by the browser. + */ + void Run(); + +#if ORTHANC_ENABLE_WASM != 1 + /** + This method must be called in order for callback handler to be allowed to + be registered. + + We'll retrieve its name and use it as the canvas name in all subsequent + calls + */ + //void RegisterSdlWindow(SDL_Window* window); + //void UnregisterSdlWindow(SDL_Window* window); +#endif + + private: + +#if ORTHANC_ENABLE_THREADS == 1 + /** + This object is used by the multithreaded Oracle to serialize access to + shared data. We need to use it as soon as we access the state. + */ + LockingEmitter& lockingEmitter_; +#endif + + /** + In SDL, this executes all the registered headers + */ + void OnAnimationFrame(); + + //void RequestAnimationFrame(OnAnimationFrameFunc func, void* userData); + std::vector<std::pair<OnAnimationFrameFunc, void*> > + animationFrameHandlers_; + + void OnResize(); + +#if ORTHANC_ENABLE_SDL == 1 + template<typename Func> + struct EventHandlerData + { + EventHandlerData(std::string canvasName, Func func, void* userData) + : canvasName(canvasName) + , func(func) + , userData(userData) + { + } + + std::string canvasName; + Func func; + void* userData; + }; + std::vector<EventHandlerData<OnWindowResizeFunc> > resizeHandlers_; + std::vector<EventHandlerData<OnMouseEventFunc > > mouseDownHandlers_; + std::vector<EventHandlerData<OnMouseEventFunc > > mouseMoveHandlers_; + std::vector<EventHandlerData<OnMouseEventFunc > > mouseUpHandlers_; + std::vector<EventHandlerData<OnMouseWheelFunc > > mouseWheelHandlers_; + + + /** + This executes all the registered headers if needed (in wasm, the browser + deals with this) + */ + void OnMouseEvent(uint32_t windowID, const GuiAdapterMouseEvent& event); + + /** + Same remark as OnMouseEvent + */ + void OnMouseWheelEvent(uint32_t windowID, const GuiAdapterWheelEvent& event); + + boost::shared_ptr<IGuiAdapterWidget> GetWidgetFromWindowId(); + +#endif + + /** + This executes all the registered headers if needed (in wasm, the browser + deals with this) + */ + void ViewportsUpdateSize(); + + std::vector<boost::weak_ptr<IGuiAdapterWidget> > widgets_; + + template<typename F> void VisitWidgets(F func) + { + for (size_t i = 0; i < widgets_.size(); i++) + { + boost::shared_ptr<IGuiAdapterWidget> widget = widgets_[i].lock(); + func(widget); + } + } + }; +}
--- a/Applications/Generic/NativeStoneApplicationContext.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Generic/NativeStoneApplicationContext.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,9 +21,9 @@ #pragma once -#include "../../Framework/Viewport/WidgetViewport.h" -#include "../../Framework/Volumes/ISlicedVolume.h" -#include "../../Framework/Volumes/IVolumeLoader.h" +#include "../../Framework/Deprecated/Viewport/WidgetViewport.h" +#include "../../Framework/Deprecated/Volumes/ISlicedVolume.h" +#include "../../Framework/Deprecated/Volumes/IVolumeLoader.h" #include <list> #include <boost/thread.hpp>
--- a/Applications/Generic/NativeStoneApplicationRunner.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Generic/NativeStoneApplicationRunner.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -19,13 +19,13 @@ **/ -#if ORTHANC_ENABLE_NATIVE != 1 -#error this file shall be included only with the ORTHANC_ENABLE_NATIVE set to 1 +#if ORTHANC_ENABLE_THREADS != 1 +#error this file shall be included only with the ORTHANC_ENABLE_THREADS set to 1 #endif #include "NativeStoneApplicationRunner.h" -#include "../../Framework/Toolbox/MessagingToolbox.h" +#include "../../Framework/Deprecated/Toolbox/MessagingToolbox.h" #include "../../Platforms/Generic/OracleWebService.h" #include "../../Platforms/Generic/OracleDelayedCallExecutor.h" #include "NativeStoneApplicationContext.h" @@ -180,7 +180,7 @@ { OrthancPlugins::OrthancHttpConnection orthanc(webServiceParameters); - if (!MessagingToolbox::CheckOrthancVersion(orthanc)) + if (!Deprecated::MessagingToolbox::CheckOrthancVersion(orthanc)) { LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of " << "Orthanc, please upgrade";
--- a/Applications/Generic/NativeStoneApplicationRunner.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Generic/NativeStoneApplicationRunner.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,8 +23,8 @@ #include "../IStoneApplication.h" -#if ORTHANC_ENABLE_NATIVE != 1 -#error this file shall be included only with the ORTHANC_ENABLE_NATIVE set to 1 +#if ORTHANC_ENABLE_THREADS != 1 +#error this file shall be included only with the ORTHANC_ENABLE_THREADS set to 1 #endif namespace OrthancStone
--- a/Applications/IStoneApplication.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/IStoneApplication.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,9 +22,10 @@ #pragma once #include "StoneApplicationContext.h" +#include "../Framework/Deprecated/Viewport/WidgetViewport.h" + #include <boost/program_options.hpp> -#include "../Framework/Viewport/WidgetViewport.h" -#include "json/json.h" +#include <json/json.h> namespace OrthancStone {
--- a/Applications/Qt/QCairoWidget.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Qt/QCairoWidget.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,8 +21,8 @@ #pragma once #include "../../Applications/Generic/NativeStoneApplicationContext.h" -#include "../../Framework/Viewport/CairoSurface.h" -#include "../../Framework/Widgets/IWidget.h" +#include "../../Framework/Wrappers/CairoSurface.h" +#include "../../Framework/Deprecated/Widgets/IWidget.h" #include <QWidget> #include <memory>
--- a/Applications/Qt/QtStoneApplicationRunner.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Qt/QtStoneApplicationRunner.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -27,7 +27,7 @@ #include <boost/program_options.hpp> #include <QApplication> -#include "../../Framework/Toolbox/MessagingToolbox.h" +#include "../../Framework/Deprecated/Toolbox/MessagingToolbox.h" #include <Core/Logging.h> #include <Core/HttpClient.h>
--- a/Applications/Samples/CMakeLists.txt Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Samples/CMakeLists.txt Mon Jun 24 14:35:00 2019 +0200 @@ -8,6 +8,9 @@ include(../../Resources/CMake/OrthancStoneParameters.cmake) +set(ENABLE_STONE_DEPRECATED ON) # Need deprecated classes for these samples + + if (OPENSSL_NO_CAPIENG) add_definitions(-DOPENSSL_NO_CAPIENG=1) endif() @@ -79,6 +82,7 @@ endif() + ##################################################################### ## Configuration for Orthanc ##################################################################### @@ -97,6 +101,10 @@ 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\"") +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + ##################################################################### ## Build a static library containing the Orthanc Stone framework @@ -208,7 +216,6 @@ ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp ${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
--- a/Applications/Samples/SampleApplicationBase.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Samples/SampleApplicationBase.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../../Applications/IStoneApplication.h" -#include "../../Framework/Widgets/WorldSceneWidget.h" +#include "../../Framework/Deprecated/Widgets/WorldSceneWidget.h" #if ORTHANC_ENABLE_WASM==1 #include "../../Platforms/Wasm/WasmPlatformApplicationAdapter.h"
--- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.h Mon Jun 24 14:35:00 2019 +0200 @@ -20,7 +20,7 @@ #pragma once -#include "Framework/Widgets/IWorldSceneInteractor.h" +#include "../../../Framework/Deprecated/Widgets/IWorldSceneInteractor.h" using namespace OrthancStone;
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -61,7 +61,7 @@ // sources smartLoader_.reset(new Deprecated::SmartLoader(IObserver::GetBroker(), context->GetOrthancApiClient())); - smartLoader_->SetImageQuality(SliceImageQuality_FullPam); + smartLoader_->SetImageQuality(Deprecated::SliceImageQuality_FullPam); mainLayout_->SetTransmitMouseOver(true); mainWidgetInteractor_.reset(new MainWidgetInteractor(*this));
--- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Samples/SimpleViewer/SimpleViewerApplication.h Mon Jun 24 14:35:00 2019 +0200 @@ -29,12 +29,12 @@ #include "Applications/IStoneApplication.h" -#include "Framework/Layers/CircleMeasureTracker.h" -#include "Framework/Layers/LineMeasureTracker.h" -#include "Framework/Widgets/SliceViewerWidget.h" -#include "Framework/Widgets/LayoutWidget.h" -#include "Framework/Messages/IObserver.h" -#include "Framework/SmartLoader.h" +#include "../../../Framework/Deprecated/Layers/CircleMeasureTracker.h" +#include "../../../Framework/Deprecated/Layers/LineMeasureTracker.h" +#include "../../../Framework/Deprecated/SmartLoader.h" +#include "../../../Framework/Deprecated/Widgets/LayoutWidget.h" +#include "../../../Framework/Deprecated/Widgets/SliceViewerWidget.h" +#include "../../../Framework/Messages/IObserver.h" #if ORTHANC_ENABLE_WASM==1 #include "Platforms/Wasm/WasmPlatformApplicationAdapter.h"
--- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Samples/SimpleViewer/ThumbnailInteractor.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,7 +21,7 @@ #pragma once -#include "Framework/Widgets/IWorldSceneInteractor.h" +#include "../../../Framework/Deprecated/Widgets/IWorldSceneInteractor.h" using namespace OrthancStone;
--- a/Applications/Samples/SimpleViewerApplicationSingleFile.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,12 +23,12 @@ #include "SampleApplicationBase.h" -#include "../../Framework/Layers/CircleMeasureTracker.h" -#include "../../Framework/Layers/LineMeasureTracker.h" -#include "../../Framework/Widgets/SliceViewerWidget.h" -#include "../../Framework/Widgets/LayoutWidget.h" +#include "../../Framework/Deprecated/Layers/CircleMeasureTracker.h" +#include "../../Framework/Deprecated/Layers/LineMeasureTracker.h" +#include "../../Framework/Deprecated/SmartLoader.h" +#include "../../Framework/Deprecated/Widgets/LayoutWidget.h" +#include "../../Framework/Deprecated/Widgets/SliceViewerWidget.h" #include "../../Framework/Messages/IObserver.h" -#include "../../Framework/SmartLoader.h" #if ORTHANC_ENABLE_WASM==1 #include "../../Platforms/Wasm/WasmPlatformApplicationAdapter.h" @@ -312,7 +312,7 @@ // sources smartLoader_.reset(new Deprecated::SmartLoader(GetBroker(), context->GetOrthancApiClient())); - smartLoader_->SetImageQuality(SliceImageQuality_FullPam); + smartLoader_->SetImageQuality(Deprecated::SliceImageQuality_FullPam); mainLayout_->SetTransmitMouseOver(true); mainWidgetInteractor_.reset(new MainWidgetInteractor(*this));
--- a/Applications/Samples/SingleFrameApplication.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Samples/SingleFrameApplication.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,8 +23,8 @@ #include "SampleApplicationBase.h" -#include "../../Framework/Layers/DicomSeriesVolumeSlicer.h" -#include "../../Framework/Widgets/SliceViewerWidget.h" +#include "../../Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h" +#include "../../Framework/Deprecated/Widgets/SliceViewerWidget.h" #include <Core/Logging.h> #include <Core/OrthancException.h>
--- a/Applications/Samples/rt-viewer-demo/CMakeLists.txt Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Samples/rt-viewer-demo/CMakeLists.txt Mon Jun 24 14:35:00 2019 +0200 @@ -81,6 +81,7 @@ endif() + ##################################################################### ## Configuration for Orthanc ##################################################################### @@ -97,6 +98,10 @@ 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\"") +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + ##################################################################### ## Build a static library containing the Orthanc Stone framework
--- a/Applications/Sdl/SdlCairoSurface.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Sdl/SdlCairoSurface.h Mon Jun 24 14:35:00 2019 +0200 @@ -24,8 +24,8 @@ #if ORTHANC_ENABLE_SDL == 1 #include "SdlWindow.h" -#include "../../Framework/Viewport/CairoSurface.h" -#include "../../Framework/Viewport/IViewport.h" +#include "../../Framework/Wrappers/CairoSurface.h" +#include "../../Framework/Deprecated/Viewport/IViewport.h" #include <boost/thread/mutex.hpp>
--- a/Applications/Sdl/SdlStoneApplicationRunner.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/Sdl/SdlStoneApplicationRunner.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -25,7 +25,6 @@ #include "SdlStoneApplicationRunner.h" -#include "../../Framework/Toolbox/MessagingToolbox.h" #include "../../Platforms/Generic/OracleWebService.h" #include "SdlEngine.h"
--- a/Applications/StoneApplicationContext.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Applications/StoneApplicationContext.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,10 +21,10 @@ #pragma once -#include "../Framework/Toolbox/IWebService.h" -#include "../Framework/Toolbox/IDelayedCallExecutor.h" -#include "../Framework/Toolbox/OrthancApiClient.h" -#include "../Framework/Viewport/WidgetViewport.h" +#include "../Framework/Deprecated/Toolbox/IWebService.h" +#include "../Framework/Deprecated/Toolbox/IDelayedCallExecutor.h" +#include "../Framework/Deprecated/Toolbox/OrthancApiClient.h" +#include "../Framework/Deprecated/Viewport/WidgetViewport.h" #ifdef _MSC_VER
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/CircleMeasureTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,106 @@ +/** + * 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 "CircleMeasureTracker.h" + +#include <stdio.h> +#include <boost/math/constants/constants.hpp> + +namespace Deprecated +{ + CircleMeasureTracker::CircleMeasureTracker(IStatusBar* statusBar, + const OrthancStone::CoordinateSystem3D& slice, + double x, + double y, + uint8_t red, + uint8_t green, + uint8_t blue, + const Orthanc::Font& font) : + statusBar_(statusBar), + slice_(slice), + x1_(x), + y1_(y), + x2_(x), + y2_(y), + font_(font) + { + color_[0] = red; + color_[1] = green; + color_[2] = blue; + } + + + void CircleMeasureTracker::Render(OrthancStone::CairoContext& context, + double zoom) + { + double x = (x1_ + x2_) / 2.0; + double y = (y1_ + y2_) / 2.0; + + OrthancStone::Vector tmp; + OrthancStone::LinearAlgebra::AssignVector(tmp, x2_ - x1_, y2_ - y1_); + double r = boost::numeric::ublas::norm_2(tmp) / 2.0; + + context.SetSourceColor(color_[0], color_[1], color_[2]); + + cairo_t* cr = context.GetObject(); + cairo_save(cr); + cairo_set_line_width(cr, 2.0 / zoom); + cairo_translate(cr, x, y); + cairo_arc(cr, 0, 0, r, 0, 2.0 * boost::math::constants::pi<double>()); + cairo_stroke_preserve(cr); + cairo_stroke(cr); + cairo_restore(cr); + + context.DrawText(font_, FormatRadius(), x, y, OrthancStone::BitmapAnchor_Center); + } + + + double CircleMeasureTracker::GetRadius() const // In millimeters + { + OrthancStone::Vector a = slice_.MapSliceToWorldCoordinates(x1_, y1_); + OrthancStone::Vector b = slice_.MapSliceToWorldCoordinates(x2_, y2_); + return boost::numeric::ublas::norm_2(b - a) / 2.0; + } + + + std::string CircleMeasureTracker::FormatRadius() const + { + char buf[64]; + sprintf(buf, "%0.01f cm", GetRadius() / 10.0); + return buf; + } + + void CircleMeasureTracker::MouseMove(int displayX, + int displayY, + double x, + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) + { + x2_ = x; + y2_ = y; + + if (statusBar_ != NULL) + { + statusBar_->SetMessage("Circle radius: " + FormatRadius()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/CircleMeasureTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,78 @@ +/** + * 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 "../Widgets/IWorldSceneMouseTracker.h" +#include "../Viewport/IStatusBar.h" +#include "../../Toolbox/CoordinateSystem3D.h" + +#include <Core/Images/Font.h> + +namespace Deprecated +{ + class CircleMeasureTracker : public IWorldSceneMouseTracker + { + private: + IStatusBar* statusBar_; + OrthancStone::CoordinateSystem3D slice_; + double x1_; + double y1_; + double x2_; + double y2_; + uint8_t color_[3]; + const Orthanc::Font& font_; + + public: + CircleMeasureTracker(IStatusBar* statusBar, + const OrthancStone::CoordinateSystem3D& slice, + double x, + double y, + uint8_t red, + uint8_t green, + uint8_t blue, + const Orthanc::Font& font); + + virtual bool HasRender() const + { + return true; + } + + virtual void Render(OrthancStone::CairoContext& context, + double zoom); + + double GetRadius() const; // In millimeters + + std::string FormatRadius() const; + + virtual void MouseUp() + { + // Possibly create a new landmark "volume" with the circle in subclasses + } + + virtual void MouseMove(int displayX, + int displayY, + double x, + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/ColorFrameRenderer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,62 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 "ColorFrameRenderer.h" + +#include <Core/OrthancException.h> +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> + +namespace Deprecated +{ + OrthancStone::CairoSurface* ColorFrameRenderer::GenerateDisplay(const RenderStyle& style) + { + std::auto_ptr<OrthancStone::CairoSurface> display + (new OrthancStone::CairoSurface(frame_->GetWidth(), frame_->GetHeight(), false /* no alpha */)); + + Orthanc::ImageAccessor target; + display->GetWriteableAccessor(target); + + Orthanc::ImageProcessing::Convert(target, *frame_); + + return display.release(); + } + + + ColorFrameRenderer::ColorFrameRenderer(const Orthanc::ImageAccessor& frame, + const OrthancStone::CoordinateSystem3D& framePlane, + double pixelSpacingX, + double pixelSpacingY, + bool isFullQuality) : + FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality), + frame_(Orthanc::Image::Clone(frame)) + { + if (frame_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (frame_->GetFormat() != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/ColorFrameRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,43 @@ +/** + * 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 "FrameRenderer.h" + +namespace Deprecated +{ + class ColorFrameRenderer : public FrameRenderer + { + private: + std::auto_ptr<Orthanc::ImageAccessor> frame_; // In RGB24 + + protected: + virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style); + + public: + ColorFrameRenderer(const Orthanc::ImageAccessor& frame, + const OrthancStone::CoordinateSystem3D& framePlane, + double pixelSpacingX, + double pixelSpacingY, + bool isFullQuality); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,162 @@ +/** + * 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 "DicomSeriesVolumeSlicer.h" + +#include "FrameRenderer.h" +#include "../Toolbox/DicomFrameConverter.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +#include <boost/lexical_cast.hpp> + +namespace Deprecated +{ + + void DicomSeriesVolumeSlicer::OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message) + { + if (message.GetOrigin().GetSlicesCount() > 0) + { + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + } + else + { + BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); + } + } + + void DicomSeriesVolumeSlicer::OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message) + { + BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); + } + + + class DicomSeriesVolumeSlicer::RendererFactory : public LayerReadyMessage::IRendererFactory + { + private: + const OrthancSlicesLoader::SliceImageReadyMessage& message_; + + public: + RendererFactory(const OrthancSlicesLoader::SliceImageReadyMessage& message) : + message_(message) + { + } + + virtual ILayerRenderer* CreateRenderer() const + { + bool isFull = (message_.GetEffectiveQuality() == SliceImageQuality_FullPng || + message_.GetEffectiveQuality() == SliceImageQuality_FullPam); + + return FrameRenderer::CreateRenderer(message_.GetImage(), message_.GetSlice(), isFull); + } + }; + + void DicomSeriesVolumeSlicer::OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message) + { + // first notify that the pixel data of the frame is ready (targeted to, i.e: an image cache) + BroadcastMessage(FrameReadyMessage(*this, message.GetImage(), + message.GetEffectiveQuality(), message.GetSlice())); + + // then notify that the layer is ready for rendering + RendererFactory factory(message); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, message.GetSlice().GetGeometry())); + } + + void DicomSeriesVolumeSlicer::OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message) + { + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, message.GetSlice().GetGeometry())); + } + + + DicomSeriesVolumeSlicer::DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker, + OrthancApiClient& orthanc) : + IVolumeSlicer(broker), + IObserver(broker), + loader_(broker, orthanc), + quality_(SliceImageQuality_FullPng) + { + loader_.RegisterObserverCallback( + new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryReadyMessage> + (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady)); + + loader_.RegisterObserverCallback( + new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryErrorMessage> + (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError)); + + loader_.RegisterObserverCallback( + new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageReadyMessage> + (*this, &DicomSeriesVolumeSlicer::OnSliceImageReady)); + + loader_.RegisterObserverCallback( + new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageErrorMessage> + (*this, &DicomSeriesVolumeSlicer::OnSliceImageError)); + } + + + void DicomSeriesVolumeSlicer::LoadSeries(const std::string& seriesId) + { + loader_.ScheduleLoadSeries(seriesId); + } + + + void DicomSeriesVolumeSlicer::LoadInstance(const std::string& instanceId) + { + loader_.ScheduleLoadInstance(instanceId); + } + + + void DicomSeriesVolumeSlicer::LoadFrame(const std::string& instanceId, + unsigned int frame) + { + loader_.ScheduleLoadFrame(instanceId, frame); + } + + + bool DicomSeriesVolumeSlicer::GetExtent(std::vector<OrthancStone::Vector>& points, + const OrthancStone::CoordinateSystem3D& viewportSlice) + { + size_t index; + + if (loader_.IsGeometryReady() && + loader_.LookupSlice(index, viewportSlice)) + { + loader_.GetSlice(index).GetExtent(points); + return true; + } + else + { + return false; + } + } + + + void DicomSeriesVolumeSlicer::ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) + { + size_t index; + + if (loader_.IsGeometryReady() && + loader_.LookupSlice(index, viewportSlice)) + { + loader_.ScheduleLoadSliceImage(index, quality_); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h Mon Jun 24 14:35: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-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 "IVolumeSlicer.h" +#include "../Toolbox/IWebService.h" +#include "../Toolbox/OrthancSlicesLoader.h" +#include "../Toolbox/OrthancApiClient.h" + +namespace Deprecated +{ + // this class is in charge of loading a Frame. + // once it's been loaded (first the geometry and then the image), + // messages are sent to observers so they can use it + class DicomSeriesVolumeSlicer : + public IVolumeSlicer, + public OrthancStone::IObserver + //private OrthancSlicesLoader::ISliceLoaderObserver + { + public: + // TODO: Add "frame" and "instanceId" + class FrameReadyMessage : public OrthancStone::OriginMessage<DicomSeriesVolumeSlicer> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const Orthanc::ImageAccessor& frame_; + SliceImageQuality imageQuality_; + const Slice& slice_; + + public: + FrameReadyMessage(DicomSeriesVolumeSlicer& origin, + const Orthanc::ImageAccessor& frame, + SliceImageQuality imageQuality, + const Slice& slice) : + OriginMessage(origin), + frame_(frame), + imageQuality_(imageQuality), + slice_(slice) + { + } + + const Orthanc::ImageAccessor& GetFrame() const + { + return frame_; + } + + SliceImageQuality GetImageQuality() const + { + return imageQuality_; + } + + const Slice& GetSlice() const + { + return slice_; + } + }; + + + private: + class RendererFactory; + + OrthancSlicesLoader loader_; + SliceImageQuality quality_; + + public: + DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker, + OrthancApiClient& orthanc); + + void LoadSeries(const std::string& seriesId); + + void LoadInstance(const std::string& instanceId); + + void LoadFrame(const std::string& instanceId, + unsigned int frame); + + void SetImageQuality(SliceImageQuality quality) + { + quality_ = quality; + } + + SliceImageQuality GetImageQuality() const + { + return quality_; + } + + size_t GetSlicesCount() const + { + return loader_.GetSlicesCount(); + } + + const Slice& GetSlice(size_t slice) const + { + return loader_.GetSlice(slice); + } + + virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, + const OrthancStone::CoordinateSystem3D& viewportSlice); + + virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice); + +protected: + void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message); + void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message); + void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message); + void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,170 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 "DicomStructureSetSlicer.h" + +namespace Deprecated +{ + class DicomStructureSetSlicer::Renderer : public ILayerRenderer + { + private: + class Structure + { + private: + bool visible_; + uint8_t red_; + uint8_t green_; + uint8_t blue_; + std::string name_; + std::vector< std::vector<OrthancStone::DicomStructureSet::PolygonPoint> > polygons_; + + public: + Structure(OrthancStone::DicomStructureSet& structureSet, + const OrthancStone::CoordinateSystem3D& plane, + size_t index) : + name_(structureSet.GetStructureName(index)) + { + structureSet.GetStructureColor(red_, green_, blue_, index); + visible_ = structureSet.ProjectStructure(polygons_, index, plane); + } + + void Render(OrthancStone::CairoContext& context) + { + if (visible_) + { + cairo_t* cr = context.GetObject(); + + context.SetSourceColor(red_, green_, blue_); + + for (size_t i = 0; i < polygons_.size(); i++) + { + cairo_move_to(cr, polygons_[i][0].first, polygons_[i][0].second); + + for (size_t j = 1; j < polygons_[i].size(); j++) + { + cairo_line_to(cr, polygons_[i][j].first, polygons_[i][j].second); + } + + cairo_line_to(cr, polygons_[i][0].first, polygons_[i][0].second); + cairo_stroke(cr); + } + } + } + }; + + typedef std::list<Structure*> Structures; + + OrthancStone::CoordinateSystem3D plane_; + Structures structures_; + + public: + Renderer(OrthancStone::DicomStructureSet& structureSet, + const OrthancStone::CoordinateSystem3D& plane) : + plane_(plane) + { + for (size_t k = 0; k < structureSet.GetStructuresCount(); k++) + { + structures_.push_back(new Structure(structureSet, plane, k)); + } + } + + virtual ~Renderer() + { + for (Structures::iterator it = structures_.begin(); + it != structures_.end(); ++it) + { + delete *it; + } + } + + virtual bool RenderLayer(OrthancStone::CairoContext& context, + const ViewportGeometry& view) + { + cairo_set_line_width(context.GetObject(), 2.0f / view.GetZoom()); + + for (Structures::const_iterator it = structures_.begin(); + it != structures_.end(); ++it) + { + assert(*it != NULL); + (*it)->Render(context); + } + + return true; + } + + virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane() + { + return plane_; + } + + virtual void SetLayerStyle(const RenderStyle& style) + { + } + + virtual bool IsFullQuality() + { + return true; + } + }; + + + class DicomStructureSetSlicer::RendererFactory : public LayerReadyMessage::IRendererFactory + { + private: + OrthancStone::DicomStructureSet& structureSet_; + const OrthancStone::CoordinateSystem3D& plane_; + + public: + RendererFactory(OrthancStone::DicomStructureSet& structureSet, + const OrthancStone::CoordinateSystem3D& plane) : + structureSet_(structureSet), + plane_(plane) + { + } + + virtual ILayerRenderer* CreateRenderer() const + { + return new Renderer(structureSet_, plane_); + } + }; + + + DicomStructureSetSlicer::DicomStructureSetSlicer(OrthancStone::MessageBroker& broker, + StructureSetLoader& loader) : + IVolumeSlicer(broker), + IObserver(broker), + loader_(loader) + { + loader_.RegisterObserverCallback( + new OrthancStone::Callable<DicomStructureSetSlicer, StructureSetLoader::ContentChangedMessage> + (*this, &DicomStructureSetSlicer::OnStructureSetLoaded)); + } + + + void DicomStructureSetSlicer::ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportPlane) + { + if (loader_.HasStructureSet()) + { + RendererFactory factory(loader_.GetStructureSet(), viewportPlane); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, viewportPlane)); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,56 @@ +/** + * 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 "IVolumeSlicer.h" +#include "../Volumes/StructureSetLoader.h" + +namespace Deprecated +{ + class DicomStructureSetSlicer : + public IVolumeSlicer, + public OrthancStone::IObserver + { + private: + class Renderer; + class RendererFactory; + + StructureSetLoader& loader_; + + void OnStructureSetLoaded(const IVolumeLoader::ContentChangedMessage& message) + { + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); + } + + public: + DicomStructureSetSlicer(OrthancStone::MessageBroker& broker, + StructureSetLoader& loader); + + virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, + const OrthancStone::CoordinateSystem3D& viewportPlane) + { + return false; + } + + virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportPlane); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/FrameRenderer.cpp Mon Jun 24 14:35: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 "FrameRenderer.h" + +#include "GrayscaleFrameRenderer.h" +#include "ColorFrameRenderer.h" + +#include <Core/OrthancException.h> + +namespace Deprecated +{ + FrameRenderer::FrameRenderer(const OrthancStone::CoordinateSystem3D& framePlane, + double pixelSpacingX, + double pixelSpacingY, + bool isFullQuality) : + framePlane_(framePlane), + pixelSpacingX_(pixelSpacingX), + pixelSpacingY_(pixelSpacingY), + isFullQuality_(isFullQuality) + { + } + + + bool FrameRenderer::RenderLayer(OrthancStone::CairoContext& context, + const ViewportGeometry& view) + { + if (!style_.visible_) + { + return true; + } + + if (display_.get() == NULL) + { + display_.reset(GenerateDisplay(style_)); + } + + assert(display_.get() != NULL); + + cairo_t *cr = context.GetObject(); + + cairo_save(cr); + + cairo_matrix_t transform; + cairo_matrix_init_identity(&transform); + cairo_matrix_scale(&transform, pixelSpacingX_, pixelSpacingY_); + cairo_matrix_translate(&transform, -0.5, -0.5); + cairo_transform(cr, &transform); + + //cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cr, display_->GetObject(), 0, 0); + + switch (style_.interpolation_) + { + case OrthancStone::ImageInterpolation_Nearest: + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); + break; + + case OrthancStone::ImageInterpolation_Bilinear: + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + cairo_paint_with_alpha(cr, style_.alpha_); + + if (style_.drawGrid_) + { + context.SetSourceColor(style_.drawColor_); + cairo_set_line_width(cr, 0.5 / view.GetZoom()); + + for (unsigned int x = 0; x <= display_->GetWidth(); x++) + { + cairo_move_to(cr, x, 0); + cairo_line_to(cr, x, display_->GetHeight()); + } + + for (unsigned int y = 0; y <= display_->GetHeight(); y++) + { + cairo_move_to(cr, 0, y); + cairo_line_to(cr, display_->GetWidth(), y); + } + + cairo_stroke(cr); + } + + cairo_restore(cr); + + return true; + } + + + void FrameRenderer::SetLayerStyle(const RenderStyle& style) + { + style_ = style; + display_.reset(NULL); + } + + + ILayerRenderer* FrameRenderer::CreateRenderer(const Orthanc::ImageAccessor& frame, + const Deprecated::Slice& framePlane, + bool isFullQuality) + { + if (frame.GetFormat() == Orthanc::PixelFormat_RGB24) + { + return new ColorFrameRenderer(frame, + framePlane.GetGeometry(), + framePlane.GetPixelSpacingX(), + framePlane.GetPixelSpacingY(), isFullQuality); + } + else + { + return new GrayscaleFrameRenderer(frame, + framePlane.GetConverter(), + framePlane.GetGeometry(), + framePlane.GetPixelSpacingX(), + framePlane.GetPixelSpacingY(), isFullQuality); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/FrameRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,69 @@ +/** + * 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 "ILayerRenderer.h" + +#include "../Toolbox/Slice.h" + +namespace Deprecated +{ + class FrameRenderer : public ILayerRenderer + { + private: + OrthancStone::CoordinateSystem3D framePlane_; + double pixelSpacingX_; + double pixelSpacingY_; + RenderStyle style_; + bool isFullQuality_; + std::auto_ptr<OrthancStone::CairoSurface> display_; + + protected: + virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style) = 0; + + public: + FrameRenderer(const OrthancStone::CoordinateSystem3D& framePlane, + double pixelSpacingX, + double pixelSpacingY, + bool isFullQuality); + + virtual bool RenderLayer(OrthancStone::CairoContext& context, + const ViewportGeometry& view); + + virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane() + { + return framePlane_; + } + + virtual void SetLayerStyle(const RenderStyle& style); + + virtual bool IsFullQuality() + { + return isFullQuality_; + } + + // TODO: Avoid cloning the "frame" + static ILayerRenderer* CreateRenderer(const Orthanc::ImageAccessor& frame, + const Deprecated::Slice& framePlane, + bool isFullQuality); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/GrayscaleFrameRenderer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,141 @@ +/** + * 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 "GrayscaleFrameRenderer.h" + +#include <Core/Images/Image.h> +#include <Core/OrthancException.h> + +namespace Deprecated +{ + OrthancStone::CairoSurface* GrayscaleFrameRenderer::GenerateDisplay(const RenderStyle& style) + { + assert(frame_->GetFormat() == Orthanc::PixelFormat_Float32); + + std::auto_ptr<OrthancStone::CairoSurface> result; + + float windowCenter, windowWidth; + style.ComputeWindowing(windowCenter, windowWidth, + defaultWindowCenter_, defaultWindowWidth_); + + float x0 = windowCenter - windowWidth / 2.0f; + float x1 = windowCenter + windowWidth / 2.0f; + + //LOG(INFO) << "Window: " << x0 << " => " << x1; + + result.reset(new OrthancStone::CairoSurface(frame_->GetWidth(), frame_->GetHeight(), false /* no alpha */)); + + const uint8_t* lut = NULL; + if (style.applyLut_) + { + if (Orthanc::EmbeddedResources::GetFileResourceSize(style.lut_) != 3 * 256) + { + // Invalid colormap + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + lut = reinterpret_cast<const uint8_t*>(Orthanc::EmbeddedResources::GetFileResourceBuffer(style.lut_)); + } + + Orthanc::ImageAccessor target; + result->GetWriteableAccessor(target); + + const unsigned int width = target.GetWidth(); + const unsigned int height = target.GetHeight(); + + for (unsigned int y = 0; y < height; y++) + { + const float* p = reinterpret_cast<const float*>(frame_->GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + + for (unsigned int x = 0; x < width; x++, p++, q += 4) + { + uint8_t v = 0; + if (windowWidth >= 0.001f) // Avoid division by zero + { + if (*p >= x1) + { + v = 255; + } + else if (*p <= x0) + { + v = 0; + } + else + { + // https://en.wikipedia.org/wiki/Linear_interpolation + v = static_cast<uint8_t>(255.0f * (*p - x0) / (x1 - x0)); + } + + if (style.reverse_ ^ (photometric_ == Orthanc::PhotometricInterpretation_Monochrome1)) + { + v = 255 - v; + } + } + + if (style.applyLut_) + { + assert(lut != NULL); + q[3] = 255; + q[2] = lut[3 * v]; + q[1] = lut[3 * v + 1]; + q[0] = lut[3 * v + 2]; + } + else + { + q[3] = 255; + q[2] = v; + q[1] = v; + q[0] = v; + } + } + } + + return result.release(); + } + + + GrayscaleFrameRenderer::GrayscaleFrameRenderer(const Orthanc::ImageAccessor& frame, + const Deprecated::DicomFrameConverter& converter, + const OrthancStone::CoordinateSystem3D& framePlane, + double pixelSpacingX, + double pixelSpacingY, + bool isFullQuality) : + FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality), + frame_(Orthanc::Image::Clone(frame)), + defaultWindowCenter_(static_cast<float>(converter.GetDefaultWindowCenter())), + defaultWindowWidth_(static_cast<float>(converter.GetDefaultWindowWidth())), + photometric_(converter.GetPhotometricInterpretation()) + { + if (frame_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + converter.ConvertFrameInplace(frame_); + assert(frame_.get() != NULL); + + if (frame_->GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/GrayscaleFrameRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,48 @@ +/** + * 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 "FrameRenderer.h" +#include "../Toolbox/DicomFrameConverter.h" + +namespace Deprecated +{ + class GrayscaleFrameRenderer : public FrameRenderer + { + private: + std::auto_ptr<Orthanc::ImageAccessor> frame_; // In Float32 + float defaultWindowCenter_; + float defaultWindowWidth_; + Orthanc::PhotometricInterpretation photometric_; + + protected: + virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style); + + public: + GrayscaleFrameRenderer(const Orthanc::ImageAccessor& frame, + const Deprecated::DicomFrameConverter& converter, + const OrthancStone::CoordinateSystem3D& framePlane, + double pixelSpacingX, + double pixelSpacingY, + bool isFullQuality); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/ILayerRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,47 @@ +/** + * 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 "../../Wrappers/CairoContext.h" +#include "../../Toolbox/CoordinateSystem3D.h" +#include "../Toolbox/ViewportGeometry.h" +#include "RenderStyle.h" + +namespace Deprecated +{ + class ILayerRenderer : public boost::noncopyable + { + public: + virtual ~ILayerRenderer() + { + } + + virtual bool RenderLayer(OrthancStone::CairoContext& context, + const ViewportGeometry& view) = 0; + + virtual void SetLayerStyle(const RenderStyle& style) = 0; + + virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane() = 0; + + virtual bool IsFullQuality() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/IVolumeSlicer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,139 @@ +/** + * 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 "ILayerRenderer.h" +#include "../Toolbox/Slice.h" +#include "../../Messages/IObservable.h" +#include "../../Messages/IMessage.h" +#include "Core/Images/Image.h" +#include <boost/shared_ptr.hpp> + +namespace Deprecated +{ + class IVolumeSlicer : public OrthancStone::IObservable + { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeSlicer); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeSlicer); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeSlicer); + + class SliceContentChangedMessage : public OrthancStone::OriginMessage<IVolumeSlicer> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const Deprecated::Slice& slice_; + + public: + SliceContentChangedMessage(IVolumeSlicer& origin, + const Deprecated::Slice& slice) : + OriginMessage(origin), + slice_(slice) + { + } + + const Deprecated::Slice& GetSlice() const + { + return slice_; + } + }; + + + class LayerReadyMessage : public OrthancStone::OriginMessage<IVolumeSlicer> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + public: + class IRendererFactory : public boost::noncopyable + { + public: + virtual ~IRendererFactory() + { + } + + virtual ILayerRenderer* CreateRenderer() const = 0; + }; + + private: + const IRendererFactory& factory_; + const OrthancStone::CoordinateSystem3D& slice_; + + public: + LayerReadyMessage(IVolumeSlicer& origin, + const IRendererFactory& rendererFactory, + const OrthancStone::CoordinateSystem3D& slice) : + OriginMessage(origin), + factory_(rendererFactory), + slice_(slice) + { + } + + ILayerRenderer* CreateRenderer() const + { + return factory_.CreateRenderer(); + } + + const OrthancStone::CoordinateSystem3D& GetSlice() const + { + return slice_; + } + }; + + + class LayerErrorMessage : public OrthancStone::OriginMessage<IVolumeSlicer> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const OrthancStone::CoordinateSystem3D& slice_; + + public: + LayerErrorMessage(IVolumeSlicer& origin, + const OrthancStone::CoordinateSystem3D& slice) : + OriginMessage(origin), + slice_(slice) + { + } + + const OrthancStone::CoordinateSystem3D& GetSlice() const + { + return slice_; + } + }; + + + IVolumeSlicer(OrthancStone::MessageBroker& broker) : + IObservable(broker) + { + } + + virtual ~IVolumeSlicer() + { + } + + virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, + const OrthancStone::CoordinateSystem3D& viewportSlice) = 0; + + virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/LineLayerRenderer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,67 @@ +/** + * 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 "LineLayerRenderer.h" + +namespace Deprecated +{ + LineLayerRenderer::LineLayerRenderer(double x1, + double y1, + double x2, + double y2, + const OrthancStone::CoordinateSystem3D& plane) : + x1_(x1), + y1_(y1), + x2_(x2), + y2_(y2), + plane_(plane) + { + RenderStyle style; + SetLayerStyle(style); + } + + + bool LineLayerRenderer::RenderLayer(OrthancStone::CairoContext& context, + const ViewportGeometry& view) + { + if (visible_) + { + context.SetSourceColor(color_); + + cairo_t *cr = context.GetObject(); + cairo_set_line_width(cr, 1.0 / view.GetZoom()); + cairo_move_to(cr, x1_, y1_); + cairo_line_to(cr, x2_, y2_); + cairo_stroke(cr); + } + + return true; + } + + + void LineLayerRenderer::SetLayerStyle(const RenderStyle& style) + { + visible_ = style.visible_; + color_[0] = style.drawColor_[0]; + color_[1] = style.drawColor_[1]; + color_[2] = style.drawColor_[2]; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/LineLayerRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,61 @@ +/** + * 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 "ILayerRenderer.h" + +namespace Deprecated +{ + class LineLayerRenderer : public ILayerRenderer + { + private: + double x1_; + double y1_; + double x2_; + double y2_; + OrthancStone::CoordinateSystem3D plane_; + bool visible_; + uint8_t color_[3]; + + public: + LineLayerRenderer(double x1, + double y1, + double x2, + double y2, + const OrthancStone::CoordinateSystem3D& plane); + + virtual bool RenderLayer(OrthancStone::CairoContext& context, + const ViewportGeometry& view); + + virtual void SetLayerStyle(const RenderStyle& style); + + virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane() + { + return plane_; + } + + virtual bool IsFullQuality() + { + return true; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/LineMeasureTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,102 @@ +/** + * 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 "LineMeasureTracker.h" + +#include <stdio.h> + +namespace Deprecated +{ + LineMeasureTracker::LineMeasureTracker(IStatusBar* statusBar, + const OrthancStone::CoordinateSystem3D& slice, + double x, + double y, + uint8_t red, + uint8_t green, + uint8_t blue, + const Orthanc::Font& font) : + statusBar_(statusBar), + slice_(slice), + x1_(x), + y1_(y), + x2_(x), + y2_(y), + font_(font) + { + color_[0] = red; + color_[1] = green; + color_[2] = blue; + } + + + void LineMeasureTracker::Render(OrthancStone::CairoContext& context, + double zoom) + { + context.SetSourceColor(color_[0], color_[1], color_[2]); + + cairo_t* cr = context.GetObject(); + cairo_set_line_width(cr, 2.0 / zoom); + cairo_move_to(cr, x1_, y1_); + cairo_line_to(cr, x2_, y2_); + cairo_stroke(cr); + + if (y2_ - y1_ < 0) + { + context.DrawText(font_, FormatLength(), x2_, y2_ - 5, OrthancStone::BitmapAnchor_BottomCenter); + } + else + { + context.DrawText(font_, FormatLength(), x2_, y2_ + 5, OrthancStone::BitmapAnchor_TopCenter); + } + } + + + double LineMeasureTracker::GetLength() const // In millimeters + { + OrthancStone::Vector a = slice_.MapSliceToWorldCoordinates(x1_, y1_); + OrthancStone::Vector b = slice_.MapSliceToWorldCoordinates(x2_, y2_); + return boost::numeric::ublas::norm_2(b - a); + } + + + std::string LineMeasureTracker::FormatLength() const + { + char buf[64]; + sprintf(buf, "%0.01f cm", GetLength() / 10.0); + return buf; + } + + void LineMeasureTracker::MouseMove(int displayX, + int displayY, + double x, + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) + { + x2_ = x; + y2_ = y; + + if (statusBar_ != NULL) + { + statusBar_->SetMessage("Line length: " + FormatLength()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/LineMeasureTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,78 @@ +/** + * 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 "../Widgets/IWorldSceneMouseTracker.h" + +#include "../Viewport/IStatusBar.h" +#include "../../Toolbox/CoordinateSystem3D.h" + +namespace Deprecated +{ + class LineMeasureTracker : public IWorldSceneMouseTracker + { + private: + IStatusBar* statusBar_; + OrthancStone::CoordinateSystem3D slice_; + double x1_; + double y1_; + double x2_; + double y2_; + uint8_t color_[3]; + unsigned int fontSize_; + const Orthanc::Font& font_; + + public: + LineMeasureTracker(IStatusBar* statusBar, + const OrthancStone::CoordinateSystem3D& slice, + double x, + double y, + uint8_t red, + uint8_t green, + uint8_t blue, + const Orthanc::Font& font); + + virtual bool HasRender() const + { + return true; + } + + virtual void Render(OrthancStone::CairoContext& context, + double zoom); + + double GetLength() const; // In millimeters + + std::string FormatLength() const; + + virtual void MouseUp() + { + // Possibly create a new landmark "volume" with the line in subclasses + } + + virtual void MouseMove(int displayX, + int displayY, + double x, + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/RenderStyle.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,106 @@ +/** + * 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 "RenderStyle.h" + +#include "../../Volumes/ImageBuffer3D.h" +#include "../Toolbox/DicomFrameConverter.h" + +#include <Core/OrthancException.h> + +namespace Deprecated +{ + RenderStyle::RenderStyle() + { + visible_ = true; + reverse_ = false; + windowing_ = OrthancStone::ImageWindowing_Custom; + alpha_ = 1; + applyLut_ = false; + lut_ = Orthanc::EmbeddedResources::COLORMAP_HOT; + drawGrid_ = false; + drawColor_[0] = 255; + drawColor_[1] = 255; + drawColor_[2] = 255; + customWindowCenter_ = 128; + customWindowWidth_ = 256; + interpolation_ = OrthancStone::ImageInterpolation_Nearest; + fontSize_ = 14; + } + + + void RenderStyle::ComputeWindowing(float& targetCenter, + float& targetWidth, + float defaultCenter, + float defaultWidth) const + { + if (windowing_ == OrthancStone::ImageWindowing_Custom) + { + targetCenter = customWindowCenter_; + targetWidth = customWindowWidth_; + } + else + { + return ::OrthancStone::ComputeWindowing + (targetCenter, targetWidth, windowing_, defaultCenter, defaultWidth); + } + } + + + void RenderStyle::SetColor(uint8_t red, + uint8_t green, + uint8_t blue) + { + drawColor_[0] = red; + drawColor_[1] = green; + drawColor_[2] = blue; + } + + + bool RenderStyle::FitRange(const OrthancStone::ImageBuffer3D& image, + const DicomFrameConverter& converter) + { + float minValue, maxValue; + + windowing_ = OrthancStone::ImageWindowing_Custom; + + if (image.GetRange(minValue, maxValue)) + { + // casting the narrower type to wider before calling the + operator + // will prevent overflowing (this is why the cast to double is only + // done on the first operand) + customWindowCenter_ = static_cast<float>( + converter.Apply((static_cast<double>(minValue) + maxValue) / 2.0)); + + customWindowWidth_ = static_cast<float>( + converter.Apply(static_cast<double>(maxValue) - minValue)); + + if (customWindowWidth_ > 1) + { + return true; + } + } + + customWindowCenter_ = 128.0; + customWindowWidth_ = 256.0; + return false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/RenderStyle.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,63 @@ +/** + * 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 "../../StoneEnumerations.h" +#include "../../Volumes/ImageBuffer3D.h" +#include "../Toolbox/DicomFrameConverter.h" + +#include <EmbeddedResources.h> + +#include <stdint.h> + +namespace Deprecated +{ + struct RenderStyle + { + bool visible_; + bool reverse_; + OrthancStone::ImageWindowing windowing_; + float alpha_; // In [0,1] + bool applyLut_; + Orthanc::EmbeddedResources::FileResourceId lut_; + bool drawGrid_; + uint8_t drawColor_[3]; + float customWindowCenter_; + float customWindowWidth_; + OrthancStone::ImageInterpolation interpolation_; + unsigned int fontSize_; + + RenderStyle(); + + void ComputeWindowing(float& targetCenter, + float& targetWidth, + float defaultCenter, + float defaultWidth) const; + + void SetColor(uint8_t red, + uint8_t green, + uint8_t blue); + + bool FitRange(const OrthancStone::ImageBuffer3D& image, + const DicomFrameConverter& converter); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/SeriesFrameRendererFactory.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,177 @@ +/** + * 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 "SeriesFrameRendererFactory.h" + +#include "FrameRenderer.h" + +#include <OrthancException.h> +#include <Logging.h> +#include <Toolbox.h> +#include <Plugins/Samples/Common/OrthancPluginException.h> +#include <Plugins/Samples/Common/DicomDatasetReader.h> + + +namespace Deprecated +{ + void SeriesFrameRendererFactory::ReadCurrentFrameDataset(size_t frame) + { + if (currentDataset_.get() != NULL && + (fast_ || currentFrame_ == frame)) + { + // The frame has not changed since the previous call, no need to + // update the DICOM dataset + return; + } + + currentDataset_.reset(loader_->DownloadDicom(frame)); + currentFrame_ = frame; + + if (currentDataset_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + void SeriesFrameRendererFactory::GetCurrentPixelSpacing(double& spacingX, + double& spacingY) const + { + if (currentDataset_.get() == NULL) + { + // There was no previous call "ReadCurrentFrameDataset()" + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + GeometryToolbox::GetPixelSpacing(spacingX, spacingY, *currentDataset_); + } + + + double SeriesFrameRendererFactory::GetCurrentSliceThickness() const + { + if (currentDataset_.get() == NULL) + { + // There was no previous call "ReadCurrentFrameDataset()" + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + try + { + OrthancPlugins::DicomDatasetReader reader(*currentDataset_); + + double thickness; + if (reader.GetDoubleValue(thickness, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS)) + { + return thickness; + } + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + } + + // Some arbitrary large slice thickness + return std::numeric_limits<double>::infinity(); + } + + + SeriesFrameRendererFactory::SeriesFrameRendererFactory(ISeriesLoader* loader, // Takes ownership + bool fast) : + loader_(loader), + currentFrame_(0), + fast_(fast) + { + if (loader == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + bool SeriesFrameRendererFactory::GetExtent(double& x1, + double& y1, + double& x2, + double& y2, + const SliceGeometry& viewportSlice) + { + if (currentDataset_.get() == NULL) + { + // There has been no previous call to + // "CreateLayerRenderer". Read some arbitrary DICOM frame, the + // one at the middle of the series. + unsigned int depth = loader_->GetGeometry().GetSliceCount(); + ReadCurrentFrameDataset(depth / 2); + } + + double spacingX, spacingY; + GetCurrentPixelSpacing(spacingX, spacingY); + + return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2, + viewportSlice, + loader_->GetGeometry().GetSlice(0), + loader_->GetWidth(), + loader_->GetHeight(), + spacingX, spacingY); + } + + + ILayerRenderer* SeriesFrameRendererFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice) + { + size_t closest; + double distance; + + bool isOpposite; + if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, loader_->GetGeometry().GetNormal(), viewportSlice.GetNormal()) || + !loader_->GetGeometry().ComputeClosestSlice(closest, distance, viewportSlice.GetOrigin())) + { + // Unable to compute the slice in the series that is the + // closest to the slice displayed by the viewport + return NULL; + } + + ReadCurrentFrameDataset(closest); + assert(currentDataset_.get() != NULL); + + double spacingX, spacingY; + GetCurrentPixelSpacing(spacingX, spacingY); + + if (distance <= GetCurrentSliceThickness() / 2.0) + { + SliceGeometry frameSlice(*currentDataset_); + return FrameRenderer::CreateRenderer(loader_->DownloadFrame(closest), + frameSlice, + *currentDataset_, + spacingX, spacingY, + true); + } + else + { + // The closest slice of the series is too far away from the + // slice displayed by the viewport + return NULL; + } + } + + + ISliceableVolume& SeriesFrameRendererFactory::GetSourceVolume() const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/SeriesFrameRendererFactory.h Mon Jun 24 14:35: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 "ILayerRendererFactory.h" + +#include "../Toolbox/ISeriesLoader.h" + +namespace Deprecated +{ + class SeriesFrameRendererFactory : public ILayerRendererFactory + { + private: + std::auto_ptr<ISeriesLoader> loader_; + size_t currentFrame_; + bool fast_; + + std::auto_ptr<OrthancPlugins::IDicomDataset> currentDataset_; + + void ReadCurrentFrameDataset(size_t frame); + + void GetCurrentPixelSpacing(double& spacingX, + double& spacingY) const; + + double GetCurrentSliceThickness() const; + + public: + SeriesFrameRendererFactory(ISeriesLoader* loader, // Takes ownership + bool fast); + + virtual bool GetExtent(double& x1, + double& y1, + double& x2, + double& y2, + const SliceGeometry& viewportSlice); + + virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice); + + virtual bool HasSourceVolume() const + { + return false; + } + + virtual ISliceableVolume& GetSourceVolume() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/SingleFrameRendererFactory.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,88 @@ +/** + * 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 "SingleFrameRendererFactory.h" + +#include "FrameRenderer.h" +#include "../Toolbox/MessagingToolbox.h" +#include "../Toolbox/DicomFrameConverter.h" + +#include <OrthancException.h> +#include <Plugins/Samples/Common/FullOrthancDataset.h> +#include <Plugins/Samples/Common/DicomDatasetReader.h> + +namespace Deprecated +{ + SingleFrameRendererFactory::SingleFrameRendererFactory(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instanceId, + unsigned int frame) : + orthanc_(orthanc), + instance_(instanceId), + frame_(frame) + { + dicom_.reset(new OrthancPlugins::FullOrthancDataset(orthanc, "/instances/" + instanceId + "/tags")); + + DicomFrameConverter converter; + converter.ReadParameters(*dicom_); + format_ = converter.GetExpectedPixelFormat(); + } + + + bool SingleFrameRendererFactory::GetExtent(double& x1, + double& y1, + double& x2, + double& y2, + const SliceGeometry& viewportSlice) + { + // Assume that PixelSpacingX == PixelSpacingY == 1 + + OrthancPlugins::DicomDatasetReader reader(*dicom_); + + unsigned int width, height; + + if (!reader.GetUnsignedIntegerValue(width, OrthancPlugins::DICOM_TAG_COLUMNS) || + !reader.GetUnsignedIntegerValue(height, OrthancPlugins::DICOM_TAG_ROWS)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + x1 = 0; + y1 = 0; + x2 = static_cast<double>(width); + y2 = static_cast<double>(height); + + return true; + } + + + ILayerRenderer* SingleFrameRendererFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice) + { + SliceGeometry frameSlice(*dicom_); + return FrameRenderer::CreateRenderer(MessagingToolbox::DecodeFrame(orthanc_, instance_, frame_, format_), + frameSlice, *dicom_, 1, 1, true); + } + + + ISliceableVolume& SingleFrameRendererFactory::GetSourceVolume() const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/SingleFrameRendererFactory.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,69 @@ +/** + * 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 "ILayerRendererFactory.h" +#include <Plugins/Samples/Common/IOrthancConnection.h> + +namespace Deprecated +{ + class SingleFrameRendererFactory : public ILayerRendererFactory + { + private: + OrthancPlugins::IOrthancConnection& orthanc_; + std::auto_ptr<OrthancPlugins::IDicomDataset> dicom_; + + std::string instance_; + unsigned int frame_; + Orthanc::PixelFormat format_; + + public: + SingleFrameRendererFactory(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instanceId, + unsigned int frame); + + const OrthancPlugins::IDicomDataset& GetDataset() const + { + return *dicom_; + } + + SliceGeometry GetSliceGeometry() + { + return SliceGeometry(*dicom_); + } + + virtual bool GetExtent(double& x1, + double& y1, + double& x2, + double& y2, + const SliceGeometry& viewportSlice); + + virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice); + + virtual bool HasSourceVolume() const + { + return false; + } + + virtual ISliceableVolume& GetSourceVolume() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/SliceOutlineRenderer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,54 @@ +/** + * 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 "SliceOutlineRenderer.h" + +namespace Deprecated +{ + bool SliceOutlineRenderer::RenderLayer(OrthancStone::CairoContext& context, + const ViewportGeometry& view) + { + if (style_.visible_) + { + cairo_t *cr = context.GetObject(); + cairo_save(cr); + + context.SetSourceColor(style_.drawColor_); + + double x1 = -0.5 * pixelSpacingX_; + double y1 = -0.5 * pixelSpacingY_; + + cairo_set_line_width(cr, 1.0 / view.GetZoom()); + cairo_rectangle(cr, x1, y1, + static_cast<double>(width_) * pixelSpacingX_, + static_cast<double>(height_) * pixelSpacingY_); + + double handleSize = 10.0f / view.GetZoom(); + cairo_move_to(cr, x1 + handleSize, y1); + cairo_line_to(cr, x1, y1 + handleSize); + + cairo_stroke(cr); + cairo_restore(cr); + } + + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Layers/SliceOutlineRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,67 @@ +/** + * 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 "ILayerRenderer.h" +#include "../Toolbox/Slice.h" + +namespace Deprecated +{ + class SliceOutlineRenderer : public ILayerRenderer + { + private: + OrthancStone::CoordinateSystem3D geometry_; + double pixelSpacingX_; + double pixelSpacingY_; + unsigned int width_; + unsigned int height_; + RenderStyle style_; + + public: + SliceOutlineRenderer(const Slice& slice) : + geometry_(slice.GetGeometry()), + pixelSpacingX_(slice.GetPixelSpacingX()), + pixelSpacingY_(slice.GetPixelSpacingY()), + width_(slice.GetWidth()), + height_(slice.GetHeight()) + { + } + + virtual bool RenderLayer(OrthancStone::CairoContext& context, + const ViewportGeometry& view); + + virtual void SetLayerStyle(const RenderStyle& style) + { + style_ = style; + } + + virtual const OrthancStone::CoordinateSystem3D& GetLayerSlice() + { + return geometry_; + } + + virtual bool IsFullQuality() + { + return true; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/SmartLoader.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,292 @@ +/** + * 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 "SmartLoader.h" + +#include "../Messages/MessageForwarder.h" +#include "../StoneException.h" +#include "Core/Images/Image.h" +#include "Core/Logging.h" +#include "Layers/DicomSeriesVolumeSlicer.h" +#include "Layers/FrameRenderer.h" +#include "Widgets/SliceViewerWidget.h" + +namespace Deprecated +{ + enum CachedSliceStatus + { + CachedSliceStatus_ScheduledToLoad, + CachedSliceStatus_GeometryLoaded, + CachedSliceStatus_ImageLoaded + }; + + class SmartLoader::CachedSlice : public IVolumeSlicer + { + public: + class RendererFactory : public LayerReadyMessage::IRendererFactory + { + private: + const CachedSlice& that_; + + public: + RendererFactory(const CachedSlice& that) : + that_(that) + { + } + + virtual ILayerRenderer* CreateRenderer() const + { + bool isFull = (that_.effectiveQuality_ == SliceImageQuality_FullPng || + that_.effectiveQuality_ == SliceImageQuality_FullPam); + + return FrameRenderer::CreateRenderer(*that_.image_, *that_.slice_, isFull); + } + }; + + unsigned int sliceIndex_; + std::auto_ptr<Slice> slice_; + boost::shared_ptr<Orthanc::ImageAccessor> image_; + SliceImageQuality effectiveQuality_; + CachedSliceStatus status_; + + public: + CachedSlice(OrthancStone::MessageBroker& broker) : + IVolumeSlicer(broker) + { + } + + virtual ~CachedSlice() + { + } + + virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, + const OrthancStone::CoordinateSystem3D& viewportSlice) + { + // TODO: viewportSlice is not used !!!! + slice_->GetExtent(points); + return true; + } + + virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) + { + // TODO: viewportSlice is not used !!!! + + // it has already been loaded -> trigger the "layer ready" message immediately otherwise, do nothing now. The LayerReady will be triggered + // once the VolumeSlicer is ready + if (status_ == CachedSliceStatus_ImageLoaded) + { + LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is loaded): " << slice_->GetOrthancInstanceId(); + + RendererFactory factory(*this); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice_->GetGeometry())); + } + else + { + LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is not loaded yet): " << slice_->GetOrthancInstanceId(); + } + } + + CachedSlice* Clone() const + { + CachedSlice* output = new CachedSlice(GetBroker()); + output->sliceIndex_ = sliceIndex_; + output->slice_.reset(slice_->Clone()); + output->image_ = image_; + output->effectiveQuality_ = effectiveQuality_; + output->status_ = status_; + + return output; + } + + }; + + + SmartLoader::SmartLoader(OrthancStone::MessageBroker& broker, + OrthancApiClient& orthancApiClient) : + IObservable(broker), + IObserver(broker), + imageQuality_(SliceImageQuality_FullPam), + orthancApiClient_(orthancApiClient) + { + } + + void SmartLoader::SetFrameInWidget(SliceViewerWidget& sliceViewer, + size_t layerIndex, + const std::string& instanceId, + unsigned int frame) + { + // TODO: check if this frame has already been loaded or is already being loaded. + // - if already loaded: create a "clone" that will emit the GeometryReady/ImageReady messages "immediately" + // (it can not be immediate because Observers needs to register first and this is done after this method returns) + // - if currently loading, we need to return an object that will observe the existing VolumeSlicer and forward + // the messages to its observables + // in both cases, we must be carefull about objects lifecycle !!! + + std::auto_ptr<IVolumeSlicer> layerSource; + std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame); + SmartLoader::CachedSlice* cachedSlice = NULL; + + if (cachedSlices_.find(sliceKeyId) != cachedSlices_.end()) // && cachedSlices_[sliceKeyId]->status_ == CachedSliceStatus_Loaded) + { + layerSource.reset(cachedSlices_[sliceKeyId]->Clone()); + cachedSlice = dynamic_cast<SmartLoader::CachedSlice*>(layerSource.get()); + } + else + { + layerSource.reset(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); + dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_); + layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady)); + layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady)); + layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady)); + dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame); + } + + // make sure that the widget registers the events before we trigger them + if (sliceViewer.GetLayerCount() == layerIndex) + { + sliceViewer.AddLayer(layerSource.release()); + } + else if (sliceViewer.GetLayerCount() > layerIndex) + { + sliceViewer.ReplaceLayer(layerIndex, layerSource.release()); + } + else + { + throw OrthancStone::StoneException(OrthancStone::ErrorCode_CanOnlyAddOneLayerAtATime); + } + + if (cachedSlice != NULL) + { + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*cachedSlice)); + } + + } + + void SmartLoader::PreloadSlice(const std::string instanceId, + unsigned int frame) + { + // TODO: reactivate -> need to be able to ScheduleLayerLoading in IVolumeSlicer without calling ScheduleLayerCreation + return; + // TODO: check if it is already in the cache + + + + // create the slice in the cache with "empty" data + boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker())); + cachedSlice->slice_.reset(new Slice(instanceId, frame)); + cachedSlice->status_ = CachedSliceStatus_ScheduledToLoad; + std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame); + + LOG(WARNING) << "Will preload: " << sliceKeyId; + + cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice); + + std::auto_ptr<IVolumeSlicer> layerSource(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); + + dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_); + layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady)); + layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady)); + layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady)); + dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame); + + // keep a ref to the VolumeSlicer until the slice is fully loaded and saved to cache + preloadingInstances_[sliceKeyId] = boost::shared_ptr<IVolumeSlicer>(layerSource.release()); + } + + +// void PreloadStudy(const std::string studyId) +// { +// /* TODO */ +// } + +// void PreloadSeries(const std::string seriesId) +// { +// /* TODO */ +// } + + + void SmartLoader::OnLayerGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message) + { + const DicomSeriesVolumeSlicer& source = + dynamic_cast<const DicomSeriesVolumeSlicer&>(message.GetOrigin()); + + // save/replace the slice in cache + const Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount() + std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + + boost::lexical_cast<std::string>(slice.GetFrame())); + + LOG(WARNING) << "Geometry ready: " << sliceKeyId; + + boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker())); + cachedSlice->slice_.reset(slice.Clone()); + cachedSlice->effectiveQuality_ = source.GetImageQuality(); + cachedSlice->status_ = CachedSliceStatus_GeometryLoaded; + + cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice); + + // re-emit original Layer message to observers + BroadcastMessage(message); + } + + + void SmartLoader::OnFrameReady(const DicomSeriesVolumeSlicer::FrameReadyMessage& message) + { + // save/replace the slice in cache + const Slice& slice = message.GetSlice(); + std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + + boost::lexical_cast<std::string>(slice.GetFrame())); + + LOG(WARNING) << "Image ready: " << sliceKeyId; + + boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker())); + cachedSlice->image_.reset(Orthanc::Image::Clone(message.GetFrame())); + cachedSlice->effectiveQuality_ = message.GetImageQuality(); + cachedSlice->slice_.reset(message.GetSlice().Clone()); + cachedSlice->status_ = CachedSliceStatus_ImageLoaded; + + cachedSlices_[sliceKeyId] = cachedSlice; + + // re-emit original Layer message to observers + BroadcastMessage(message); + } + + + void SmartLoader::OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message) + { + const DicomSeriesVolumeSlicer& source = + dynamic_cast<const DicomSeriesVolumeSlicer&>(message.GetOrigin()); + + const Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount() ? + std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + + boost::lexical_cast<std::string>(slice.GetFrame())); + + LOG(WARNING) << "Layer ready: " << sliceKeyId; + + // remove the slice from the preloading slices now that it has been fully loaded and it is referenced in the cache + if (preloadingInstances_.find(sliceKeyId) != preloadingInstances_.end()) + { + preloadingInstances_.erase(sliceKeyId); + } + + // re-emit original Layer message to observers + BroadcastMessage(message); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/SmartLoader.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,67 @@ +/** + * 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 "Layers/DicomSeriesVolumeSlicer.h" +#include "../Messages/IObservable.h" +#include "Toolbox/OrthancApiClient.h" + +namespace Deprecated +{ + class SliceViewerWidget; + + class SmartLoader : public OrthancStone::IObservable, public OrthancStone::IObserver + { + class CachedSlice; + + protected: + typedef std::map<std::string, boost::shared_ptr<SmartLoader::CachedSlice> > CachedSlices; + CachedSlices cachedSlices_; + + typedef std::map<std::string, boost::shared_ptr<IVolumeSlicer> > PreloadingInstances; + PreloadingInstances preloadingInstances_; + + SliceImageQuality imageQuality_; + OrthancApiClient& orthancApiClient_; + + public: + SmartLoader(OrthancStone::MessageBroker& broker, OrthancApiClient& orthancApiClient); // TODO: add maxPreloadStorageSizeInBytes + +// void PreloadStudy(const std::string studyId); +// void PreloadSeries(const std::string seriesId); + void PreloadSlice(const std::string instanceId, unsigned int frame); + + void SetImageQuality(SliceImageQuality imageQuality) { imageQuality_ = imageQuality; } + + void SetFrameInWidget(SliceViewerWidget& sliceViewer, size_t layerIndex, const std::string& instanceId, unsigned int frame); + + void GetFirstInstanceIdForSeries(std::string& output, const std::string& seriesId); + + private: + void OnLayerGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message); + void OnFrameReady(const DicomSeriesVolumeSlicer::FrameReadyMessage& message); + void OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message); + + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/BaseWebService.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,145 @@ +/** + * 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 "BaseWebService.h" + +#include "../../Messages/IObservable.h" +#include "../../../Platforms/Generic/IOracleCommand.h" + +#include <Core/OrthancException.h> + +#include <boost/shared_ptr.hpp> + +namespace Deprecated +{ + + + class BaseWebService::BaseWebServicePayload : public Orthanc::IDynamicObject + { + private: + std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage> > userSuccessHandler_; + std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> > userFailureHandler_; + std::auto_ptr< Orthanc::IDynamicObject> userPayload_; + + public: + BaseWebServicePayload(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* userSuccessHandler, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* userFailureHandler, + Orthanc::IDynamicObject* userPayload) : + userSuccessHandler_(userSuccessHandler), + userFailureHandler_(userFailureHandler), + userPayload_(userPayload) + { + } + + void HandleSuccess(const IWebService::HttpRequestSuccessMessage& message) const + { + if (userSuccessHandler_.get() != NULL) + { + // recreate a success message with the user payload + IWebService::HttpRequestSuccessMessage successMessage(message.GetUri(), + message.GetAnswer(), + message.GetAnswerSize(), + message.GetAnswerHttpHeaders(), + userPayload_.get()); + userSuccessHandler_->Apply(successMessage); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + void HandleFailure(const IWebService::HttpRequestErrorMessage& message) const + { + if (userFailureHandler_.get() != NULL) + { + // recreate a failure message with the user payload + IWebService::HttpRequestErrorMessage failureMessage(message.GetUri(), + userPayload_.get()); + + userFailureHandler_->Apply(failureMessage); + } + } + + }; + + + void BaseWebService::GetAsync(const std::string& uri, + const HttpHeaders& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, + unsigned int timeoutInSeconds) + { + if (cache_.find(uri) == cache_.end()) + { + GetAsyncInternal(uri, headers, + new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered + new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestSuccessMessage> + (*this, &BaseWebService::CacheAndNotifyHttpSuccess), + new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestErrorMessage> + (*this, &BaseWebService::NotifyHttpError), + timeoutInSeconds); + } + else + { + // create a command and "post" it to the Oracle so it is executed and commited "later" + NotifyHttpSuccessLater(cache_[uri], payload, successCallback); + } + + } + + + + void BaseWebService::NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message) + { + if (message.HasPayload()) + { + dynamic_cast<const BaseWebServicePayload&>(message.GetPayload()).HandleSuccess(message); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + void BaseWebService::CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message) + { + cache_[message.GetUri()] = boost::shared_ptr<CachedHttpRequestSuccessMessage>(new CachedHttpRequestSuccessMessage(message)); + NotifyHttpSuccess(message); + } + + void BaseWebService::NotifyHttpError(const IWebService::HttpRequestErrorMessage& message) + { + if (message.HasPayload()) + { + dynamic_cast<const BaseWebServicePayload&>(message.GetPayload()).HandleFailure(message); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/BaseWebService.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,131 @@ +/** + * 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 "IWebService.h" + +#include <string> +#include <map> + +namespace Deprecated +{ + // This is an intermediate of IWebService that implements some caching on + // the HTTP GET requests + class BaseWebService : public IWebService, public OrthancStone::IObserver + { + public: + class CachedHttpRequestSuccessMessage + { + protected: + std::string uri_; + void* answer_; + size_t answerSize_; + IWebService::HttpHeaders answerHeaders_; + + public: + CachedHttpRequestSuccessMessage(const IWebService::HttpRequestSuccessMessage& message) : + uri_(message.GetUri()), + answerSize_(message.GetAnswerSize()), + answerHeaders_(message.GetAnswerHttpHeaders()) + { + answer_ = malloc(answerSize_); + memcpy(answer_, message.GetAnswer(), answerSize_); + } + + ~CachedHttpRequestSuccessMessage() + { + free(answer_); + } + + const std::string& GetUri() const + { + return uri_; + } + + const void* GetAnswer() const + { + return answer_; + } + + size_t GetAnswerSize() const + { + return answerSize_; + } + + const IWebService::HttpHeaders& GetAnswerHttpHeaders() const + { + return answerHeaders_; + } + + }; + protected: + class BaseWebServicePayload; + + bool cacheEnabled_; + std::map<std::string, boost::shared_ptr<CachedHttpRequestSuccessMessage> > cache_; // TODO: this is currently an infinite cache ! + + public: + + BaseWebService(OrthancStone::MessageBroker& broker) : + IWebService(broker), + IObserver(broker), + cacheEnabled_(true) + { + } + + virtual ~BaseWebService() + { + } + + virtual void EnableCache(bool enable) + { + cacheEnabled_ = enable; + } + + virtual void GetAsync(const std::string& uri, + const HttpHeaders& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + unsigned int timeoutInSeconds = 60); + + protected: + virtual void GetAsyncInternal(const std::string& uri, + const HttpHeaders& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + unsigned int timeoutInSeconds = 60) = 0; + + virtual void NotifyHttpSuccessLater(boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedHttpMessage, + Orthanc::IDynamicObject* payload, // takes ownership + OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback) = 0; + + private: + void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message); + + void NotifyHttpError(const IWebService::HttpRequestErrorMessage& message); + + void CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message); + + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/DicomFrameConverter.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,282 @@ +/** + * 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 "DicomFrameConverter.h" + +#include "../../Toolbox/LinearAlgebra.h" + +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +namespace Deprecated +{ + static const Orthanc::DicomTag IMAGE_TAGS[] = + { + Orthanc::DICOM_TAG_BITS_STORED, + Orthanc::DICOM_TAG_DOSE_GRID_SCALING, + Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION, + Orthanc::DICOM_TAG_PIXEL_REPRESENTATION, + Orthanc::DICOM_TAG_RESCALE_INTERCEPT, + Orthanc::DICOM_TAG_RESCALE_SLOPE, + Orthanc::DICOM_TAG_WINDOW_CENTER, + Orthanc::DICOM_TAG_WINDOW_WIDTH + }; + + + void DicomFrameConverter::SetDefaultParameters() + { + isSigned_ = true; + isColor_ = false; + hasRescale_ = false; + rescaleIntercept_ = 0; + rescaleSlope_ = 1; + hasDefaultWindow_ = false; + defaultWindowCenter_ = 128; + defaultWindowWidth_ = 256; + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + } + + + void DicomFrameConverter::ReadParameters(const Orthanc::DicomMap& dicom) + { + SetDefaultParameters(); + + OrthancStone::Vector c, w; + if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && + OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && + c.size() > 0 && + w.size() > 0) + { + hasDefaultWindow_ = true; + defaultWindowCenter_ = static_cast<float>(c[0]); + defaultWindowWidth_ = static_cast<float>(w[0]); + } + + int32_t tmp; + if (!dicom.ParseInteger32(tmp, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION)) + { + // Type 1 tag, must be present + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + isSigned_ = (tmp == 1); + + double doseGridScaling; + bool isRTDose = false; + + if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && + dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) + { + hasRescale_ = true; + } + else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) + { + // This is for RT-DOSE + hasRescale_ = true; + isRTDose = true; + rescaleIntercept_ = 0; + rescaleSlope_ = doseGridScaling; + + if (!dicom.ParseInteger32(tmp, Orthanc::DICOM_TAG_BITS_STORED)) + { + // Type 1 tag, must be present + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + switch (tmp) + { + case 16: + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + break; + + case 32: + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + std::string photometric; + if (dicom.CopyToString(photometric, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION, false)) + { + photometric = Orthanc::Toolbox::StripSpaces(photometric); + } + else + { + // Type 1 tag, must be present + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + photometric_ = Orthanc::StringToPhotometricInterpretation(photometric.c_str()); + + isColor_ = (photometric != "MONOCHROME1" && + photometric != "MONOCHROME2"); + + // TODO Add more checks, e.g. on the number of bytes per value + // (cf. DicomImageInformation.h in Orthanc) + + if (!isRTDose) + { + if (isColor_) + { + expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; + } + else if (isSigned_) + { + expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; + } + else + { + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + } + } + } + + + void DicomFrameConverter::ReadParameters(const OrthancPlugins::IDicomDataset& dicom) + { + Orthanc::DicomMap converted; + + for (size_t i = 0; i < sizeof(IMAGE_TAGS) / sizeof(Orthanc::DicomTag); i++) + { + OrthancPlugins::DicomTag tag(IMAGE_TAGS[i].GetGroup(), IMAGE_TAGS[i].GetElement()); + + std::string value; + if (dicom.GetStringValue(value, tag)) + { + converted.SetValue(IMAGE_TAGS[i], value, false); + } + } + + ReadParameters(converted); + } + + + void DicomFrameConverter::ConvertFrameInplace(std::auto_ptr<Orthanc::ImageAccessor>& source) const + { + assert(sizeof(float) == 4); + + if (source.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (source->GetFormat() == GetExpectedPixelFormat() && + source->GetFormat() == Orthanc::PixelFormat_RGB24) + { + // No conversion has to be done, check out (*) + return; + } + else + { + source.reset(ConvertFrame(*source)); + } + } + + + Orthanc::ImageAccessor* DicomFrameConverter::ConvertFrame(const Orthanc::ImageAccessor& source) const + { + assert(sizeof(float) == 4); + + Orthanc::PixelFormat sourceFormat = source.GetFormat(); + + if (sourceFormat != GetExpectedPixelFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (sourceFormat == Orthanc::PixelFormat_RGB24) + { + // This is the case of a color image. No conversion has to be done (*) + std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_RGB24, + source.GetWidth(), + source.GetHeight(), + false)); + Orthanc::ImageProcessing::Copy(*converted, source); + return converted.release(); + } + else + { + assert(sourceFormat == Orthanc::PixelFormat_Grayscale16 || + sourceFormat == Orthanc::PixelFormat_Grayscale32 || + sourceFormat == Orthanc::PixelFormat_SignedGrayscale16); + + // This is the case of a grayscale frame. Convert it to Float32. + std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, + source.GetWidth(), + source.GetHeight(), + false)); + Orthanc::ImageProcessing::Convert(*converted, source); + + // Correct rescale slope/intercept if need be + ApplyRescale(*converted, sourceFormat != Orthanc::PixelFormat_Grayscale32); + + return converted.release(); + } + } + + + void DicomFrameConverter::ApplyRescale(Orthanc::ImageAccessor& image, + bool useDouble) const + { + if (image.GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (hasRescale_) + { + for (unsigned int y = 0; y < image.GetHeight(); y++) + { + float* p = reinterpret_cast<float*>(image.GetRow(y)); + + if (useDouble) + { + // Slower, accurate implementation using double + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + double value = static_cast<double>(*p); + *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_); + } + } + else + { + // Fast, approximate implementation using float + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_); + } + } + } + } + } + + + double DicomFrameConverter::Apply(double x) const + { + return x * rescaleSlope_ + rescaleIntercept_; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/DicomFrameConverter.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,169 @@ +/** + * 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 <Plugins/Samples/Common/IDicomDataset.h> +#include <Core/DicomFormat/DicomMap.h> +#include <Core/Images/ImageAccessor.h> + +#include <memory> + +namespace Deprecated +{ + /** + * This class is responsible for converting the pixel format of a + * DICOM frame coming from Orthanc, into a pixel format that is + * suitable for Stone, given the relevant DICOM tags: + * - Color frames will stay in the RGB24 format. + * - Grayscale frames will be converted to the Float32 format. + **/ + class DicomFrameConverter + { + private: + bool isSigned_; + bool isColor_; + bool hasRescale_; + double rescaleIntercept_; + double rescaleSlope_; + bool hasDefaultWindow_; + double defaultWindowCenter_; + double defaultWindowWidth_; + + Orthanc::PhotometricInterpretation photometric_; + Orthanc::PixelFormat expectedPixelFormat_; + + void SetDefaultParameters(); + + public: + DicomFrameConverter() + { + 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; + } + + // AM: this is required to serialize/deserialize it + DicomFrameConverter( + bool isSigned, + bool isColor, + bool hasRescale, + double rescaleIntercept, + double rescaleSlope, + bool hasDefaultWindow, + double defaultWindowCenter, + double defaultWindowWidth, + Orthanc::PhotometricInterpretation photometric, + Orthanc::PixelFormat expectedPixelFormat + ): + isSigned_(isSigned), + isColor_(isColor), + hasRescale_(hasRescale), + rescaleIntercept_(rescaleIntercept), + rescaleSlope_(rescaleSlope), + hasDefaultWindow_(hasDefaultWindow), + defaultWindowCenter_(defaultWindowCenter), + defaultWindowWidth_(defaultWindowWidth), + photometric_(photometric), + expectedPixelFormat_(expectedPixelFormat) + {} + + void GetParameters(bool& isSigned, + bool& isColor, + bool& hasRescale, + double& rescaleIntercept, + double& rescaleSlope, + bool& hasDefaultWindow, + double& defaultWindowCenter, + double& defaultWindowWidth, + Orthanc::PhotometricInterpretation& photometric, + Orthanc::PixelFormat& expectedPixelFormat) const + { + isSigned = isSigned_; + isColor = isColor_; + hasRescale = hasRescale_; + rescaleIntercept = rescaleIntercept_; + rescaleSlope = rescaleSlope_; + hasDefaultWindow = hasDefaultWindow_; + defaultWindowCenter = defaultWindowCenter_; + defaultWindowWidth = defaultWindowWidth_; + photometric = photometric_; + expectedPixelFormat = expectedPixelFormat_; + } + + Orthanc::PixelFormat GetExpectedPixelFormat() const + { + return expectedPixelFormat_; + } + + Orthanc::PhotometricInterpretation GetPhotometricInterpretation() const + { + return photometric_; + } + + void ReadParameters(const Orthanc::DicomMap& dicom); + + void ReadParameters(const OrthancPlugins::IDicomDataset& dicom); + + bool HasDefaultWindow() const + { + return hasDefaultWindow_; + } + + double GetDefaultWindowCenter() const + { + return defaultWindowCenter_; + } + + double GetDefaultWindowWidth() const + { + return defaultWindowWidth_; + } + + double GetRescaleIntercept() const + { + return rescaleIntercept_; + } + + double GetRescaleSlope() const + { + return rescaleSlope_; + } + + void ConvertFrameInplace(std::auto_ptr<Orthanc::ImageAccessor>& source) const; + + Orthanc::ImageAccessor* ConvertFrame(const Orthanc::ImageAccessor& source) const; + + void ApplyRescale(Orthanc::ImageAccessor& image, + bool useDouble) const; + + double Apply(double x) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/DownloadStack.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,196 @@ +/** + * 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 "DownloadStack.h" + +#include <Core/OrthancException.h> + +#include <cassert> + +namespace Deprecated +{ + bool DownloadStack::CheckInvariants() const + { + std::vector<bool> dequeued(nodes_.size(), true); + + int i = firstNode_; + while (i != NIL) + { + const Node& node = nodes_[i]; + + dequeued[i] = false; + + if (node.next_ != NIL && + nodes_[node.next_].prev_ != i) + { + return false; + } + + if (node.prev_ != NIL && + nodes_[node.prev_].next_ != i) + { + return false; + } + + i = nodes_[i].next_; + } + + for (size_t i = 0; i < nodes_.size(); i++) + { + if (nodes_[i].dequeued_ != dequeued[i]) + { + return false; + } + } + + return true; + } + + + DownloadStack::DownloadStack(unsigned int size) + { + nodes_.resize(size); + + if (size == 0) + { + firstNode_ = NIL; + } + else + { + for (size_t i = 0; i < size; i++) + { + nodes_[i].prev_ = static_cast<int>(i - 1); + nodes_[i].next_ = static_cast<int>(i + 1); + nodes_[i].dequeued_ = false; + } + + nodes_.front().prev_ = NIL; + nodes_.back().next_ = NIL; + firstNode_ = 0; + } + + assert(CheckInvariants()); + } + + + DownloadStack::~DownloadStack() + { + assert(CheckInvariants()); + } + + + bool DownloadStack::Pop(unsigned int& value) + { + assert(CheckInvariants()); + + if (firstNode_ == NIL) + { + for (size_t i = 0; i < nodes_.size(); i++) + { + assert(nodes_[i].dequeued_); + } + + return false; + } + else + { + assert(firstNode_ >= 0 && firstNode_ < static_cast<int>(nodes_.size())); + value = firstNode_; + + Node& node = nodes_[firstNode_]; + assert(node.prev_ == NIL); + assert(!node.dequeued_); + + node.dequeued_ = true; + firstNode_ = node.next_; + + if (firstNode_ != NIL) + { + nodes_[firstNode_].prev_ = NIL; + } + + return true; + } + } + + + void DownloadStack::SetTopNodeInternal(unsigned int value) + { + assert(CheckInvariants()); + + Node& node = nodes_[value]; + + if (node.dequeued_) + { + // This node has already been processed by the download thread, nothing to do + return; + } + + // Remove the node from the list + if (node.prev_ == NIL) + { + assert(firstNode_ == static_cast<int>(value)); + + // This is already the top node in the list, nothing to do + return; + } + + nodes_[node.prev_].next_ = node.next_; + + if (node.next_ != NIL) + { + nodes_[node.next_].prev_ = node.prev_; + } + + // Add back the node at the top of the list + assert(firstNode_ != NIL); + + Node& old = nodes_[firstNode_]; + assert(old.prev_ == NIL); + assert(!old.dequeued_); + node.prev_ = NIL; + node.next_ = firstNode_; + old.prev_ = value; + + firstNode_ = value; + } + + + void DownloadStack::SetTopNode(unsigned int value) + { + if (value >= nodes_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + SetTopNodeInternal(value); + } + + + void DownloadStack::SetTopNodePermissive(int value) + { + if (value >= 0 && + value < static_cast<int>(nodes_.size())) + { + SetTopNodeInternal(value); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/DownloadStack.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,60 @@ +/** + * 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 <vector> +#include <boost/noncopyable.hpp> + +namespace Deprecated +{ + class DownloadStack : public boost::noncopyable + { + private: + static const int NIL = -1; + + // This is a doubly-linked list + struct Node + { + int next_; + int prev_; + bool dequeued_; + }; + + std::vector<Node> nodes_; + int firstNode_; + + bool CheckInvariants() const; + + void SetTopNodeInternal(unsigned int value); + + public: + DownloadStack(unsigned int size); + + ~DownloadStack(); + + bool Pop(unsigned int& value); + + void SetTopNode(unsigned int value); + + void SetTopNodePermissive(int value); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,58 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Messages/IObserver.h" +#include "../../Messages/ICallable.h" + +#include <Core/IDynamicObject.h> +#include <Core/Logging.h> + +#include <string> +#include <map> + +namespace Deprecated +{ + // The IDelayedCall executes a callback after a delay (equivalent to timeout() function in javascript). + class IDelayedCallExecutor : public boost::noncopyable + { + protected: + OrthancStone::MessageBroker& broker_; + + public: + ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(__FILE__, __LINE__, TimeoutMessage); + + IDelayedCallExecutor(OrthancStone::MessageBroker& broker) : + broker_(broker) + { + } + + + virtual ~IDelayedCallExecutor() + { + } + + + virtual void Schedule(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback, + unsigned int timeoutInMs = 1000) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/ISeriesLoader.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,59 @@ +/** + * 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 "ParallelSlices.h" + +#include <Images/ImageAccessor.h> +#include <Plugins/Samples/Common/IDicomDataset.h> + +namespace Deprecated +{ + class ISeriesLoader : public boost::noncopyable + { + public: + virtual ~ISeriesLoader() + { + } + + virtual ParallelSlices& GetGeometry() = 0; + + virtual Orthanc::PixelFormat GetPixelFormat() = 0; + + virtual unsigned int GetWidth() = 0; + + virtual unsigned int GetHeight() = 0; + + virtual OrthancPlugins::IDicomDataset* DownloadDicom(size_t index) = 0; + + // This downloads the frame from Orthanc. The resulting pixel + // format must be Grayscale8, Grayscale16, SignedGrayscale16 or + // RGB24. Orthanc Stone assumes the conversion of the photometric + // interpretation is done by Orthanc. + virtual Orthanc::ImageAccessor* DownloadFrame(size_t index) = 0; + + virtual Orthanc::ImageAccessor* DownloadJpegFrame(size_t index, + unsigned int quality) = 0; + + virtual bool IsJpegAvailable() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/IWebService.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,55 @@ +/** + * 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 "IWebService.h" + +#include <Core/OrthancException.h> + + +namespace Deprecated +{ + const Orthanc::IDynamicObject& + IWebService::HttpRequestSuccessMessage::GetPayload() const + { + if (HasPayload()) + { + return *payload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + const Orthanc::IDynamicObject& + IWebService::HttpRequestErrorMessage::GetPayload() const + { + if (HasPayload()) + { + return *payload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/IWebService.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,166 @@ +/** + * 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 "../../Messages/IObserver.h" +#include "../../Messages/ICallable.h" + +#include <Core/IDynamicObject.h> +#include <Core/Logging.h> + +#include <string> +#include <map> + +namespace Deprecated +{ + // The IWebService performs HTTP requests. + // Since applications can run in native or WASM environment and, since + // in a WASM environment, the WebService is asynchronous, the IWebservice + // also implements an asynchronous interface: you must schedule a request + // and you'll be notified when the response/error is ready. + class IWebService : public boost::noncopyable + { + protected: + OrthancStone::MessageBroker& broker_; + + public: + typedef std::map<std::string, std::string> HttpHeaders; + + class HttpRequestSuccessMessage : public OrthancStone::IMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const std::string& uri_; + const void* answer_; + size_t answerSize_; + const HttpHeaders& answerHeaders_; + const Orthanc::IDynamicObject* payload_; + + public: + HttpRequestSuccessMessage(const std::string& uri, + const void* answer, + size_t answerSize, + const HttpHeaders& answerHeaders, + const Orthanc::IDynamicObject* payload) : + uri_(uri), + answer_(answer), + answerSize_(answerSize), + answerHeaders_(answerHeaders), + payload_(payload) + { + } + + const std::string& GetUri() const + { + return uri_; + } + + const void* GetAnswer() const + { + return answer_; + } + + size_t GetAnswerSize() const + { + return answerSize_; + } + + const HttpHeaders& GetAnswerHttpHeaders() const + { + return answerHeaders_; + } + + bool HasPayload() const + { + return payload_ != NULL; + } + + const Orthanc::IDynamicObject& GetPayload() const; + }; + + + class HttpRequestErrorMessage : public OrthancStone::IMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const std::string& uri_; + const Orthanc::IDynamicObject* payload_; + + public: + HttpRequestErrorMessage(const std::string& uri, + const Orthanc::IDynamicObject* payload) : + uri_(uri), + payload_(payload) + { + } + + const std::string& GetUri() const + { + return uri_; + } + + bool HasPayload() const + { + return payload_ != NULL; + } + + const Orthanc::IDynamicObject& GetPayload() const; + }; + + + IWebService(OrthancStone::MessageBroker& broker) : + broker_(broker) + { + } + + + virtual ~IWebService() + { + } + + virtual void EnableCache(bool enable) = 0; + + virtual void GetAsync(const std::string& uri, + const HttpHeaders& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + unsigned int timeoutInSeconds = 60) = 0; + + virtual void PostAsync(const std::string& uri, + const HttpHeaders& headers, + const std::string& body, + Orthanc::IDynamicObject* payload /* takes ownership */, + OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + unsigned int timeoutInSeconds = 60) = 0; + + virtual void DeleteAsync(const std::string& uri, + const HttpHeaders& headers, + Orthanc::IDynamicObject* payload /* takes ownership */, + OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + unsigned int timeoutInSeconds = 60) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/MessagingToolbox.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,456 @@ +/** + * 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 "MessagingToolbox.h" + +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Images/JpegReader.h> +#include <Core/Images/PngReader.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> +#include <Core/Logging.h> + +#include <boost/lexical_cast.hpp> +#include <json/reader.h> +#include <json/writer.h> + +namespace Deprecated +{ + namespace MessagingToolbox + { + static bool ParseVersion(std::string& version, + unsigned int& major, + unsigned int& minor, + unsigned int& patch, + const Json::Value& info) + { + if (info.type() != Json::objectValue || + !info.isMember("Version") || + info["Version"].type() != Json::stringValue) + { + return false; + } + + version = info["Version"].asString(); + if (version == "mainline") + { + // Some arbitrary high values Orthanc versions will never reach ;) + major = 999; + minor = 999; + patch = 999; + return true; + } + + std::vector<std::string> tokens; + Orthanc::Toolbox::TokenizeString(tokens, version, '.'); + + if (tokens.size() != 2 && + tokens.size() != 3) + { + return false; + } + + int a, b, c; + try + { + a = boost::lexical_cast<int>(tokens[0]); + b = boost::lexical_cast<int>(tokens[1]); + + if (tokens.size() == 3) + { + c = boost::lexical_cast<int>(tokens[2]); + } + else + { + c = 0; + } + } + catch (boost::bad_lexical_cast&) + { + return false; + } + + if (a < 0 || + b < 0 || + c < 0) + { + return false; + } + else + { + major = static_cast<unsigned int>(a); + minor = static_cast<unsigned int>(b); + patch = static_cast<unsigned int>(c); + return true; + } + } + + + bool ParseJson(Json::Value& target, + const void* content, + size_t size) + { + Json::Reader reader; + return reader.parse(reinterpret_cast<const char*>(content), + reinterpret_cast<const char*>(content) + size, + target); + } + + void JsonToString(std::string& target, + const Json::Value& source) + { + Json::FastWriter writer; + target = writer.write(source); + } + + static void ParseJsonException(Json::Value& target, + const std::string& source) + { + Json::Reader reader; + if (!reader.parse(source, target)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + void RestApiGet(Json::Value& target, + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& uri) + { + std::string tmp; + orthanc.RestApiGet(tmp, uri); + ParseJsonException(target, tmp); + } + + + void RestApiPost(Json::Value& target, + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& uri, + const std::string& body) + { + std::string tmp; + orthanc.RestApiPost(tmp, uri, body); + ParseJsonException(target, tmp); + } + + + bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc) + { + try + { + Json::Value json; + RestApiGet(json, orthanc, "/plugins/web-viewer"); + return json.type() == Json::objectValue; + } + catch (Orthanc::OrthancException&) + { + return false; + } + } + + + bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc) + { + Json::Value json; + std::string version; + unsigned int major, minor, patch; + + try + { + RestApiGet(json, orthanc, "/system"); + } + catch (Orthanc::OrthancException&) + { + LOG(ERROR) << "Cannot connect to your Orthanc server"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + if (!ParseVersion(version, major, minor, patch, json)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + LOG(WARNING) << "Version of the Orthanc core (must be above 1.3.1): " << version; + + // Stone is only compatible with Orthanc >= 1.3.1 + if (major < 1 || + (major == 1 && minor < 3) || + (major == 1 && minor == 3 && patch < 1)) + { + return false; + } + + try + { + RestApiGet(json, orthanc, "/plugins/web-viewer"); + } + catch (Orthanc::OrthancException&) + { + // The Web viewer is not installed, this is OK + LOG(WARNING) << "The Web viewer plugin is not installed, progressive download is disabled"; + return true; + } + + if (!ParseVersion(version, major, minor, patch, json)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + LOG(WARNING) << "Version of the Web viewer plugin (must be above 2.2): " << version; + + return (major >= 3 || + (major == 2 && minor >= 2)); + } + + + Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instance, + unsigned int frame, + Orthanc::PixelFormat targetFormat) + { + std::string uri = ("instances/" + instance + "/frames/" + + boost::lexical_cast<std::string>(frame)); + + std::string compressed; + + switch (targetFormat) + { + case Orthanc::PixelFormat_RGB24: + orthanc.RestApiGet(compressed, uri + "/preview"); + break; + + case Orthanc::PixelFormat_Grayscale16: + orthanc.RestApiGet(compressed, uri + "/image-uint16"); + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + orthanc.RestApiGet(compressed, uri + "/image-int16"); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::auto_ptr<Orthanc::PngReader> result(new Orthanc::PngReader); + result->ReadFromMemory(compressed); + + if (targetFormat == Orthanc::PixelFormat_SignedGrayscale16) + { + if (result->GetFormat() == Orthanc::PixelFormat_Grayscale16) + { + result->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + return result.release(); + } + + + Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instance, + unsigned int frame, + unsigned int quality, + Orthanc::PixelFormat targetFormat) + { + if (quality <= 0 || + quality > 100) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + // This requires the official Web viewer plugin to be installed! + std::string uri = ("web-viewer/instances/jpeg" + + boost::lexical_cast<std::string>(quality) + + "-" + instance + "_" + + boost::lexical_cast<std::string>(frame)); + + Json::Value encoded; + RestApiGet(encoded, orthanc, uri); + + if (encoded.type() != Json::objectValue || + !encoded.isMember("Orthanc") || + encoded["Orthanc"].type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + Json::Value& info = encoded["Orthanc"]; + if (!info.isMember("PixelData") || + !info.isMember("Stretched") || + !info.isMember("Compression") || + info["Compression"].type() != Json::stringValue || + info["PixelData"].type() != Json::stringValue || + info["Stretched"].type() != Json::booleanValue || + info["Compression"].asString() != "Jpeg") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + bool isSigned = false; + bool isStretched = info["Stretched"].asBool(); + + if (info.isMember("IsSigned")) + { + if (info["IsSigned"].type() != Json::booleanValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + else + { + isSigned = info["IsSigned"].asBool(); + } + } + + std::string jpeg; + Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); + + std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader); + reader->ReadFromMemory(jpeg); + + if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image + { + if (targetFormat != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + if (isSigned || isStretched) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + else + { + return reader.release(); + } + } + + if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + if (!isStretched) + { + if (targetFormat != reader->GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + return reader.release(); + } + + int32_t stretchLow = 0; + int32_t stretchHigh = 0; + + if (!info.isMember("StretchLow") || + !info.isMember("StretchHigh") || + info["StretchLow"].type() != Json::intValue || + info["StretchHigh"].type() != Json::intValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + stretchLow = info["StretchLow"].asInt(); + stretchHigh = info["StretchHigh"].asInt(); + + if (stretchLow < -32768 || + stretchHigh > 65535 || + (stretchLow < 0 && stretchHigh > 32767)) + { + // This range cannot be represented with a uint16_t or an int16_t + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + // Decode a grayscale JPEG 8bpp image coming from the Web viewer + std::auto_ptr<Orthanc::ImageAccessor> image + (new Orthanc::Image(targetFormat, reader->GetWidth(), reader->GetHeight(), false)); + + float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; + float offset = static_cast<float>(stretchLow) / scaling; + + Orthanc::ImageProcessing::Convert(*image, *reader); + Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); + +#if 0 + /*info.removeMember("PixelData"); + std::cout << info.toStyledString();*/ + + int64_t a, b; + Orthanc::ImageProcessing::GetMinMaxValue(a, b, *image); + std::cout << stretchLow << "->" << stretchHigh << " = " << a << "->" << b << std::endl; +#endif + + return image.release(); + } + + + static void AddTag(Orthanc::DicomMap& target, + const OrthancPlugins::IDicomDataset& source, + const Orthanc::DicomTag& tag) + { + OrthancPlugins::DicomTag key(tag.GetGroup(), tag.GetElement()); + + std::string value; + if (source.GetStringValue(value, key)) + { + target.SetValue(tag, value, false); + } + } + + + void ConvertDataset(Orthanc::DicomMap& target, + const OrthancPlugins::IDicomDataset& source) + { + target.Clear(); + + AddTag(target, source, Orthanc::DICOM_TAG_BITS_ALLOCATED); + AddTag(target, source, Orthanc::DICOM_TAG_BITS_STORED); + AddTag(target, source, Orthanc::DICOM_TAG_COLUMNS); + AddTag(target, source, Orthanc::DICOM_TAG_DOSE_GRID_SCALING); + AddTag(target, source, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER); + AddTag(target, source, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR); + AddTag(target, source, Orthanc::DICOM_TAG_HIGH_BIT); + AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT); + AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT); + AddTag(target, source, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES); + AddTag(target, source, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION); + AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION); + AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_SPACING); + AddTag(target, source, Orthanc::DICOM_TAG_PLANAR_CONFIGURATION); + AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); + AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_SLOPE); + AddTag(target, source, Orthanc::DICOM_TAG_ROWS); + AddTag(target, source, Orthanc::DICOM_TAG_SAMPLES_PER_PIXEL); + AddTag(target, source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); + AddTag(target, source, Orthanc::DICOM_TAG_SLICE_THICKNESS); + AddTag(target, source, Orthanc::DICOM_TAG_SOP_CLASS_UID); + AddTag(target, source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID); + AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_CENTER); + AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_WIDTH); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/MessagingToolbox.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,75 @@ +/** + * 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 "../../StoneEnumerations.h" + +#include <Core/DicomFormat/DicomMap.h> +#include <Core/Images/ImageAccessor.h> +#include <Plugins/Samples/Common/IDicomDataset.h> +#include <Plugins/Samples/Common/IOrthancConnection.h> + +#include <json/value.h> + +namespace Deprecated +{ + namespace MessagingToolbox + { + bool ParseJson(Json::Value& target, + const void* content, + size_t size); + + void JsonToString(std::string& target, + const Json::Value& source); + + + void RestApiGet(Json::Value& target, + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& uri); + + void RestApiPost(Json::Value& target, + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& uri, + const std::string& body); + + bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc); + + bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc); + + // This downloads the image from Orthanc and keeps its pixel + // format unchanged (will be either Grayscale8, Grayscale16, + // SignedGrayscale16, or RGB24) + Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instance, + unsigned int frame, + Orthanc::PixelFormat targetFormat); + + Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instance, + unsigned int frame, + unsigned int quality, + Orthanc::PixelFormat targetFormat); + + void ConvertDataset(Orthanc::DicomMap& target, + const OrthancPlugins::IDicomDataset& source); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,336 @@ +/** + * 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 "OrthancApiClient.h" + +#include "../Toolbox/MessagingToolbox.h" + +#include <Core/OrthancException.h> + +namespace Deprecated +{ + const Orthanc::IDynamicObject& OrthancApiClient::JsonResponseReadyMessage::GetPayload() const + { + if (HasPayload()) + { + return *payload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + const Orthanc::IDynamicObject& OrthancApiClient::BinaryResponseReadyMessage::GetPayload() const + { + if (HasPayload()) + { + return *payload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + const Orthanc::IDynamicObject& OrthancApiClient::EmptyResponseReadyMessage::GetPayload() const + { + if (HasPayload()) + { + return *payload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + class OrthancApiClient::WebServicePayload : public Orthanc::IDynamicObject + { + private: + std::auto_ptr< OrthancStone::MessageHandler<EmptyResponseReadyMessage> > emptyHandler_; + std::auto_ptr< OrthancStone::MessageHandler<JsonResponseReadyMessage> > jsonHandler_; + std::auto_ptr< OrthancStone::MessageHandler<BinaryResponseReadyMessage> > binaryHandler_; + std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> > failureHandler_; + std::auto_ptr< Orthanc::IDynamicObject > userPayload_; + + void NotifyConversionError(const IWebService::HttpRequestSuccessMessage& message) const + { + if (failureHandler_.get() != NULL) + { + failureHandler_->Apply(IWebService::HttpRequestErrorMessage + (message.GetUri(), userPayload_.get())); + } + } + + public: + WebServicePayload(OrthancStone::MessageHandler<EmptyResponseReadyMessage>* handler, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, + Orthanc::IDynamicObject* userPayload) : + emptyHandler_(handler), + failureHandler_(failureHandler), + userPayload_(userPayload) + { + if (handler == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + WebServicePayload(OrthancStone::MessageHandler<BinaryResponseReadyMessage>* handler, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, + Orthanc::IDynamicObject* userPayload) : + binaryHandler_(handler), + failureHandler_(failureHandler), + userPayload_(userPayload) + { + if (handler == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + WebServicePayload(OrthancStone::MessageHandler<JsonResponseReadyMessage>* handler, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, + Orthanc::IDynamicObject* userPayload) : + jsonHandler_(handler), + failureHandler_(failureHandler), + userPayload_(userPayload) + { + if (handler == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + void HandleSuccess(const IWebService::HttpRequestSuccessMessage& message) const + { + if (emptyHandler_.get() != NULL) + { + emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage + (message.GetUri(), userPayload_.get())); + } + else if (binaryHandler_.get() != NULL) + { + binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage + (message.GetUri(), message.GetAnswer(), + message.GetAnswerSize(), userPayload_.get())); + } + else if (jsonHandler_.get() != NULL) + { + Json::Value response; + if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) + { + jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage + (message.GetUri(), response, userPayload_.get())); + } + else + { + NotifyConversionError(message); + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + void HandleFailure(const IWebService::HttpRequestErrorMessage& message) const + { + if (failureHandler_.get() != NULL) + { + failureHandler_->Apply(IWebService::HttpRequestErrorMessage + (message.GetUri(), userPayload_.get())); + } + } + }; + + + OrthancApiClient::OrthancApiClient(OrthancStone::MessageBroker& broker, + IWebService& web, + const std::string& baseUrl) : + IObservable(broker), + IObserver(broker), + web_(web), + baseUrl_(baseUrl) + { + } + + + void OrthancApiClient::GetJsonAsync( + const std::string& uri, + OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, + Orthanc::IDynamicObject* payload) + { + IWebService::HttpHeaders emptyHeaders; + web_.GetAsync(baseUrl_ + uri, + emptyHeaders, + new WebServicePayload(successCallback, failureCallback, payload), + new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> + (*this, &OrthancApiClient::NotifyHttpSuccess), + new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> + (*this, &OrthancApiClient::NotifyHttpError)); + } + + + void OrthancApiClient::GetBinaryAsync( + const std::string& uri, + const std::string& contentType, + OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, + Orthanc::IDynamicObject* payload) + { + IWebService::HttpHeaders headers; + headers["Accept"] = contentType; + GetBinaryAsync(uri, headers, successCallback, failureCallback, payload); + } + + void OrthancApiClient::GetBinaryAsync( + const std::string& uri, + const IWebService::HttpHeaders& headers, + OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, + Orthanc::IDynamicObject* payload) + { + // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str()); + + web_.GetAsync(baseUrl_ + uri, headers, + new WebServicePayload(successCallback, failureCallback, payload), + new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> + (*this, &OrthancApiClient::NotifyHttpSuccess), + new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> + (*this, &OrthancApiClient::NotifyHttpError)); + } + + + void OrthancApiClient::PostBinaryAsyncExpectJson( + const std::string& uri, + const std::string& body, + OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, + Orthanc::IDynamicObject* payload) + { + web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, + new WebServicePayload(successCallback, failureCallback, payload), + new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> + (*this, &OrthancApiClient::NotifyHttpSuccess), + new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> + (*this, &OrthancApiClient::NotifyHttpError)); + + } + + void OrthancApiClient::PostBinaryAsync( + const std::string& uri, + const std::string& body) + { + web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, NULL, NULL, NULL); + } + + void OrthancApiClient::PostBinaryAsync( + const std::string& uri, + const std::string& body, + OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, + Orthanc::IDynamicObject* payload /* takes ownership */) + { + web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, + new WebServicePayload(successCallback, failureCallback, payload), + new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> + (*this, &OrthancApiClient::NotifyHttpSuccess), + new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> + (*this, &OrthancApiClient::NotifyHttpError)); + } + + void OrthancApiClient::PostJsonAsyncExpectJson( + const std::string& uri, + const Json::Value& data, + OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, + Orthanc::IDynamicObject* payload) + { + std::string body; + MessagingToolbox::JsonToString(body, data); + return PostBinaryAsyncExpectJson(uri, body, successCallback, failureCallback, payload); + } + + void OrthancApiClient::PostJsonAsync( + const std::string& uri, + const Json::Value& data) + { + std::string body; + MessagingToolbox::JsonToString(body, data); + return PostBinaryAsync(uri, body); + } + + void OrthancApiClient::PostJsonAsync( + const std::string& uri, + const Json::Value& data, + OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, + OrthancStone::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, + OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, + Orthanc::IDynamicObject* payload) + { + web_.DeleteAsync(baseUrl_ + uri, IWebService::HttpHeaders(), + new WebServicePayload(successCallback, failureCallback, payload), + new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> + (*this, &OrthancApiClient::NotifyHttpSuccess), + new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> + (*this, &OrthancApiClient::NotifyHttpError)); + } + + + void OrthancApiClient::NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message) + { + if (message.HasPayload()) + { + dynamic_cast<const WebServicePayload&>(message.GetPayload()).HandleSuccess(message); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + void OrthancApiClient::NotifyHttpError(const IWebService::HttpRequestErrorMessage& message) + { + if (message.HasPayload()) + { + dynamic_cast<const WebServicePayload&>(message.GetPayload()).HandleFailure(message); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/OrthancApiClient.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,243 @@ +/** + * 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/shared_ptr.hpp> +#include <json/json.h> + +#include "IWebService.h" +#include "../../Messages/IObservable.h" +#include "../../Messages/Promise.h" + +namespace Deprecated +{ + class OrthancApiClient : + public OrthancStone::IObservable, + public OrthancStone::IObserver + { + public: + class JsonResponseReadyMessage : public OrthancStone::IMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const std::string& uri_; + const Json::Value& json_; + const Orthanc::IDynamicObject* payload_; + + public: + JsonResponseReadyMessage(const std::string& uri, + const Json::Value& json, + const Orthanc::IDynamicObject* payload) : + uri_(uri), + json_(json), + payload_(payload) + { + } + + const std::string& GetUri() const + { + return uri_; + } + + const Json::Value& GetJson() const + { + return json_; + } + + bool HasPayload() const + { + return payload_ != NULL; + } + + const Orthanc::IDynamicObject& GetPayload() const; + }; + + + class BinaryResponseReadyMessage : public OrthancStone::IMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const std::string& uri_; + const void* answer_; + size_t answerSize_; + const Orthanc::IDynamicObject* payload_; + + public: + BinaryResponseReadyMessage(const std::string& uri, + const void* answer, + size_t answerSize, + const Orthanc::IDynamicObject* payload) : + uri_(uri), + answer_(answer), + answerSize_(answerSize), + payload_(payload) + { + } + + const std::string& GetUri() const + { + return uri_; + } + + const void* GetAnswer() const + { + return answer_; + } + + size_t GetAnswerSize() const + { + return answerSize_; + } + + bool HasPayload() const + { + return payload_ != NULL; + } + + const Orthanc::IDynamicObject& GetPayload() const; + }; + + + class EmptyResponseReadyMessage : public OrthancStone::IMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const std::string& uri_; + const Orthanc::IDynamicObject* payload_; + + public: + EmptyResponseReadyMessage(const std::string& uri, + const Orthanc::IDynamicObject* payload) : + uri_(uri), + payload_(payload) + { + } + + const std::string& GetUri() const + { + return uri_; + } + + bool HasPayload() const + { + return payload_ != NULL; + } + + const Orthanc::IDynamicObject& GetPayload() const; + }; + + + + private: + class WebServicePayload; + + protected: + IWebService& web_; + std::string baseUrl_; + + public: + OrthancApiClient(OrthancStone::MessageBroker& broker, + IWebService& web, + const std::string& baseUrl); + + virtual ~OrthancApiClient() + { + } + + const std::string& GetBaseUrl() const {return baseUrl_;} + + // schedule a GET request expecting a JSON response. + void GetJsonAsync(const std::string& uri, + OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL /* takes ownership */); + + // schedule a GET request expecting a binary response. + void GetBinaryAsync(const std::string& uri, + const std::string& contentType, + OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL /* takes ownership */); + + // schedule a GET request expecting a binary response. + void GetBinaryAsync(const std::string& uri, + const IWebService::HttpHeaders& headers, + OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL /* takes ownership */); + + // schedule a POST request expecting a JSON response. + void PostBinaryAsyncExpectJson(const std::string& uri, + const std::string& body, + OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL /* takes ownership */); + + // schedule a POST request expecting a JSON response. + void PostJsonAsyncExpectJson(const std::string& uri, + const Json::Value& data, + OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL /* takes ownership */); + + // schedule a POST request and don't mind the response. + 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, + OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, + OrthancStone::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, + OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, + OrthancStone::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, + OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, + OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL /* takes ownership */); + + void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message); + + void NotifyHttpError(const IWebService::HttpRequestErrorMessage& message); + + private: + void HandleFromCache(const std::string& uri, + const IWebService::HttpHeaders& headers, + Orthanc::IDynamicObject* payload /* takes ownership */); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,904 @@ +/** + * 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 "OrthancSlicesLoader.h" + +#include "../Toolbox/MessagingToolbox.h" + +#include <Core/Compression/GzipCompressor.h> +#include <Core/Endianness.h> +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Images/JpegReader.h> +#include <Core/Images/PngReader.h> +#include <Core/Images/PamReader.h> +#include <Core/Logging.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> +#include <Plugins/Samples/Common/FullOrthancDataset.h> + +#include <boost/lexical_cast.hpp> + + + +/** + * TODO This is a SLOW implementation of base64 decoding, because + * "Orthanc::Toolbox::DecodeBase64()" does not work properly with + * WASM. UNDERSTAND WHY. + * https://stackoverflow.com/a/34571089/881731 + **/ +static std::string base64_decode(const std::string &in) +{ + std::string out; + + std::vector<int> T(256,-1); + for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; + + int val=0, valb=-8; + for (size_t i = 0; i < in.size(); i++) { + unsigned char c = in[i]; + if (T[c] == -1) break; + val = (val<<6) + T[c]; + valb += 6; + if (valb>=0) { + out.push_back(char((val>>valb)&0xFF)); + valb-=8; + } + } + return out; +} + + + +namespace Deprecated +{ + class OrthancSlicesLoader::Operation : public Orthanc::IDynamicObject + { + private: + Mode mode_; + unsigned int frame_; + unsigned int sliceIndex_; + const Slice* slice_; + std::string instanceId_; + SliceImageQuality quality_; + + Operation(Mode mode) : + mode_(mode) + { + } + + public: + Mode GetMode() const + { + return mode_; + } + + SliceImageQuality GetQuality() const + { + assert(mode_ == Mode_LoadImage || + mode_ == Mode_LoadRawImage); + return quality_; + } + + unsigned int GetSliceIndex() const + { + assert(mode_ == Mode_LoadImage || + mode_ == Mode_LoadRawImage); + return sliceIndex_; + } + + const Slice& GetSlice() const + { + assert(mode_ == Mode_LoadImage || + mode_ == Mode_LoadRawImage); + assert(slice_ != NULL); + return *slice_; + } + + unsigned int GetFrame() const + { + assert(mode_ == Mode_FrameGeometry); + return frame_; + } + + const std::string& GetInstanceId() const + { + assert(mode_ == Mode_FrameGeometry || + mode_ == Mode_InstanceGeometry); + return instanceId_; + } + + static Operation* DownloadInstanceGeometry(const std::string& instanceId) + { + std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry)); + operation->instanceId_ = instanceId; + return operation.release(); + } + + static Operation* DownloadFrameGeometry(const std::string& instanceId, + unsigned int frame) + { + std::auto_ptr<Operation> operation(new Operation(Mode_FrameGeometry)); + operation->instanceId_ = instanceId; + operation->frame_ = frame; + return operation.release(); + } + + static Operation* DownloadSliceImage(unsigned int sliceIndex, + const Slice& slice, + SliceImageQuality quality) + { + std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage)); + tmp->sliceIndex_ = sliceIndex; + tmp->slice_ = &slice; + tmp->quality_ = quality; + return tmp.release(); + } + + static Operation* DownloadSliceRawImage(unsigned int sliceIndex, + const Slice& slice) + { + std::auto_ptr<Operation> tmp(new Operation(Mode_LoadRawImage)); + tmp->sliceIndex_ = sliceIndex; + tmp->slice_ = &slice; + tmp->quality_ = SliceImageQuality_InternalRaw; + return tmp.release(); + } + + static Operation* DownloadDicomFile(const Slice& slice) + { + std::auto_ptr<Operation> tmp(new Operation(Mode_LoadDicomFile)); + tmp->slice_ = &slice; + return tmp.release(); + } + + }; + + void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation, + const Orthanc::ImageAccessor& image) + { + OrthancSlicesLoader::SliceImageReadyMessage msg + (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality()); + BroadcastMessage(msg); + } + + + void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) + { + OrthancSlicesLoader::SliceImageErrorMessage msg + (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality()); + BroadcastMessage(msg); + } + + + void OrthancSlicesLoader::SortAndFinalizeSlices() + { + bool ok = slices_.Sort(); + + state_ = State_GeometryReady; + + if (ok) + { + LOG(INFO) << "Loaded a series with " << slices_.GetSlicesCount() << " slice(s)"; + BroadcastMessage(SliceGeometryReadyMessage(*this)); + } + else + { + LOG(ERROR) << "This series is empty"; + BroadcastMessage(SliceGeometryErrorMessage(*this)); + } + } + + void OrthancSlicesLoader::OnGeometryError(const IWebService::HttpRequestErrorMessage& message) + { + BroadcastMessage(SliceGeometryErrorMessage(*this)); + state_ = State_Error; + } + + void OrthancSlicesLoader::OnSliceImageError(const IWebService::HttpRequestErrorMessage& message) + { + NotifySliceImageError(dynamic_cast<const Operation&>(message.GetPayload())); + state_ = State_Error; + } + + void OrthancSlicesLoader::ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message) + { + const Json::Value& series = message.GetJson(); + Json::Value::Members instances = series.getMemberNames(); + + slices_.Reserve(instances.size()); + + for (size_t i = 0; i < instances.size(); i++) + { + OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]); + + Orthanc::DicomMap dicom; + MessagingToolbox::ConvertDataset(dicom, dataset); + + unsigned int frames; + if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) + { + frames = 1; + } + + for (unsigned int frame = 0; frame < frames; frame++) + { + std::auto_ptr<Slice> slice(new Slice); + if (slice->ParseOrthancFrame(dicom, instances[i], frame)) + { + OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry(); + slices_.AddSlice(geometry, slice.release()); + } + else + { + LOG(WARNING) << "Skipping invalid frame " << frame << " within instance " << instances[i]; + } + } + } + + SortAndFinalizeSlices(); + } + + void OrthancSlicesLoader::ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message) + { + const Json::Value& tags = message.GetJson(); + const std::string& instanceId = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetInstanceId(); + + OrthancPlugins::FullOrthancDataset dataset(tags); + + Orthanc::DicomMap dicom; + MessagingToolbox::ConvertDataset(dicom, dataset); + + unsigned int frames; + if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) + { + frames = 1; + } + + LOG(INFO) << "Instance " << instanceId << " contains " << frames << " frame(s)"; + + for (unsigned int frame = 0; frame < frames; frame++) + { + std::auto_ptr<Slice> slice(new Slice); + if (slice->ParseOrthancFrame(dicom, instanceId, frame)) + { + OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry(); + slices_.AddSlice(geometry, slice.release()); + } + else + { + LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId; + BroadcastMessage(SliceGeometryErrorMessage(*this)); + return; + } + } + + SortAndFinalizeSlices(); + } + + + void OrthancSlicesLoader::ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message) + { + const Json::Value& tags = message.GetJson(); + const std::string& instanceId = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetInstanceId(); + unsigned int frame = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetFrame(); + + OrthancPlugins::FullOrthancDataset dataset(tags); + + state_ = State_GeometryReady; + + Orthanc::DicomMap dicom; + MessagingToolbox::ConvertDataset(dicom, dataset); + + std::auto_ptr<Slice> slice(new Slice); + if (slice->ParseOrthancFrame(dicom, instanceId, frame)) + { + LOG(INFO) << "Loaded instance geometry " << instanceId; + + OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry(); + slices_.AddSlice(geometry, slice.release()); + + BroadcastMessage(SliceGeometryReadyMessage(*this)); + } + else + { + LOG(WARNING) << "Skipping invalid instance " << instanceId; + BroadcastMessage(SliceGeometryErrorMessage(*this)); + } + } + + + void OrthancSlicesLoader::ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message) + { + const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()); + std::auto_ptr<Orthanc::ImageAccessor> image; + + try + { + image.reset(new Orthanc::PngReader); + dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(message.GetAnswer(), message.GetAnswerSize()); + } + catch (Orthanc::OrthancException&) + { + NotifySliceImageError(operation); + return; + } + + if (image->GetWidth() != operation.GetSlice().GetWidth() || + image->GetHeight() != operation.GetSlice().GetHeight()) + { + NotifySliceImageError(operation); + return; + } + + if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() == + Orthanc::PixelFormat_SignedGrayscale16) + { + if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16) + { + image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + } + else + { + NotifySliceImageError(operation); + return; + } + } + + NotifySliceImageSuccess(operation, *image); + } + + void OrthancSlicesLoader::ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message) + { + const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()); + std::auto_ptr<Orthanc::ImageAccessor> image; + + try + { + image.reset(new Orthanc::PamReader); + dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(message.GetAnswer(), message.GetAnswerSize()); + } + catch (Orthanc::OrthancException&) + { + NotifySliceImageError(operation); + return; + } + + if (image->GetWidth() != operation.GetSlice().GetWidth() || + image->GetHeight() != operation.GetSlice().GetHeight()) + { + NotifySliceImageError(operation); + return; + } + + if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() == + Orthanc::PixelFormat_SignedGrayscale16) + { + if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16) + { + image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + } + else + { + NotifySliceImageError(operation); + return; + } + } + + NotifySliceImageSuccess(operation, *image); + } + + + void OrthancSlicesLoader::ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message) + { + const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()); + + const Json::Value& encoded = message.GetJson(); + if (encoded.type() != Json::objectValue || + !encoded.isMember("Orthanc") || + encoded["Orthanc"].type() != Json::objectValue) + { + NotifySliceImageError(operation); + return; + } + + const Json::Value& info = encoded["Orthanc"]; + if (!info.isMember("PixelData") || + !info.isMember("Stretched") || + !info.isMember("Compression") || + info["Compression"].type() != Json::stringValue || + info["PixelData"].type() != Json::stringValue || + info["Stretched"].type() != Json::booleanValue || + info["Compression"].asString() != "Jpeg") + { + NotifySliceImageError(operation); + return; + } + + bool isSigned = false; + bool isStretched = info["Stretched"].asBool(); + + if (info.isMember("IsSigned")) + { + if (info["IsSigned"].type() != Json::booleanValue) + { + NotifySliceImageError(operation); + return; + } + else + { + isSigned = info["IsSigned"].asBool(); + } + } + + std::auto_ptr<Orthanc::ImageAccessor> reader; + + { + std::string jpeg; + //Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); + jpeg = base64_decode(info["PixelData"].asString()); + + try + { + reader.reset(new Orthanc::JpegReader); + dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg); + } + catch (Orthanc::OrthancException&) + { + NotifySliceImageError(operation); + return; + } + } + + Orthanc::PixelFormat expectedFormat = + operation.GetSlice().GetConverter().GetExpectedPixelFormat(); + + if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image + { + if (expectedFormat != Orthanc::PixelFormat_RGB24) + { + NotifySliceImageError(operation); + return; + } + + if (isSigned || isStretched) + { + NotifySliceImageError(operation); + return; + } + else + { + NotifySliceImageSuccess(operation, *reader); + return; + } + } + + if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) + { + NotifySliceImageError(operation); + return; + } + + if (!isStretched) + { + if (expectedFormat != reader->GetFormat()) + { + NotifySliceImageError(operation); + return; + } + else + { + NotifySliceImageSuccess(operation, *reader); + return; + } + } + + int32_t stretchLow = 0; + int32_t stretchHigh = 0; + + if (!info.isMember("StretchLow") || + !info.isMember("StretchHigh") || + info["StretchLow"].type() != Json::intValue || + info["StretchHigh"].type() != Json::intValue) + { + NotifySliceImageError(operation); + return; + } + + stretchLow = info["StretchLow"].asInt(); + stretchHigh = info["StretchHigh"].asInt(); + + if (stretchLow < -32768 || + stretchHigh > 65535 || + (stretchLow < 0 && stretchHigh > 32767)) + { + // This range cannot be represented with a uint16_t or an int16_t + NotifySliceImageError(operation); + return; + } + + // Decode a grayscale JPEG 8bpp image coming from the Web viewer + std::auto_ptr<Orthanc::ImageAccessor> image + (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false)); + + Orthanc::ImageProcessing::Convert(*image, *reader); + reader.reset(); + + float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; + + if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling)) + { + float offset = static_cast<float>(stretchLow) / scaling; + Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); + } + + NotifySliceImageSuccess(operation, *image); + } + + + class StringImage : public Orthanc::ImageAccessor + { + private: + std::string buffer_; + + public: + StringImage(Orthanc::PixelFormat format, + unsigned int width, + unsigned int height, + std::string& buffer) + { + if (buffer.size() != Orthanc::GetBytesPerPixel(format) * width * height) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + buffer_.swap(buffer); // The source buffer is now empty + + void* data = (buffer_.empty() ? NULL : &buffer_[0]); + + AssignWritable(format, width, height, + Orthanc::GetBytesPerPixel(format) * width, data); + } + }; + + void OrthancSlicesLoader::ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message) + { + const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()); + Orthanc::GzipCompressor compressor; + + std::string raw; + compressor.Uncompress(raw, message.GetAnswer(), message.GetAnswerSize()); + + const Orthanc::DicomImageInformation& info = operation.GetSlice().GetImageInformation(); + + if (info.GetBitsAllocated() == 32 && + info.GetBitsStored() == 32 && + info.GetHighBit() == 31 && + info.GetChannelCount() == 1 && + !info.IsSigned() && + info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 && + raw.size() == info.GetWidth() * info.GetHeight() * 4) + { + // This is the case of RT-DOSE (uint32_t values) + + std::auto_ptr<Orthanc::ImageAccessor> image + (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(), + info.GetHeight(), raw)); + + // TODO - Only for big endian + for (unsigned int y = 0; y < image->GetHeight(); y++) + { + uint32_t *p = reinterpret_cast<uint32_t*>(image->GetRow(y)); + for (unsigned int x = 0; x < image->GetWidth(); x++, p++) + { + *p = le32toh(*p); + } + } + + NotifySliceImageSuccess(operation, *image); + } + else if (info.GetBitsAllocated() == 16 && + info.GetBitsStored() == 16 && + info.GetHighBit() == 15 && + info.GetChannelCount() == 1 && + !info.IsSigned() && + info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 && + raw.size() == info.GetWidth() * info.GetHeight() * 2) + { + std::auto_ptr<Orthanc::ImageAccessor> image + (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(), + info.GetHeight(), raw)); + + // TODO - Big endian ? + + NotifySliceImageSuccess(operation, *image); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + } + + + OrthancSlicesLoader::OrthancSlicesLoader(OrthancStone::MessageBroker& broker, + OrthancApiClient& orthanc) : + OrthancStone::IObservable(broker), + OrthancStone::IObserver(broker), + orthanc_(orthanc), + state_(State_Initialization) + { + } + + + void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId) + { + if (state_ != State_Initialization) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + state_ = State_LoadingGeometry; + orthanc_.GetJsonAsync("/series/" + seriesId + "/instances-tags", + new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSeriesGeometry), + new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError), + NULL); + } + } + + void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId) + { + if (state_ != State_Initialization) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + state_ = State_LoadingGeometry; + + // Tag "3004-000c" is "Grid Frame Offset Vector", which is + // mandatory to read RT DOSE, but is too long to be returned by default + orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c", + new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseInstanceGeometry), + new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError), + Operation::DownloadInstanceGeometry(instanceId)); + } + } + + + void OrthancSlicesLoader::ScheduleLoadFrame(const std::string& instanceId, + unsigned int frame) + { + if (state_ != State_Initialization) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + state_ = State_LoadingGeometry; + + orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags", + new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseFrameGeometry), + new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError), + Operation::DownloadFrameGeometry(instanceId, frame)); + } + } + + + bool OrthancSlicesLoader::IsGeometryReady() const + { + return state_ == State_GeometryReady; + } + + + size_t OrthancSlicesLoader::GetSlicesCount() const + { + if (state_ != State_GeometryReady) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return slices_.GetSlicesCount(); + } + + + const Slice& OrthancSlicesLoader::GetSlice(size_t index) const + { + if (state_ != State_GeometryReady) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return dynamic_cast<const Slice&>(slices_.GetSlicePayload(index)); + } + + + bool OrthancSlicesLoader::LookupSlice(size_t& index, + const OrthancStone::CoordinateSystem3D& plane) const + { + if (state_ != State_GeometryReady) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + double distance; + return (slices_.LookupClosestSlice(index, distance, plane) && + distance <= GetSlice(index).GetThickness() / 2.0); + } + + + void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice, + size_t index) + { + std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + + boost::lexical_cast<std::string>(slice.GetFrame())); + + switch (slice.GetConverter().GetExpectedPixelFormat()) + { + case Orthanc::PixelFormat_RGB24: + uri += "/preview"; + break; + + case Orthanc::PixelFormat_Grayscale16: + uri += "/image-uint16"; + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + uri += "/image-int16"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + orthanc_.GetBinaryAsync(uri, "image/png", + new OrthancStone::Callable<OrthancSlicesLoader, + OrthancApiClient::BinaryResponseReadyMessage> + (*this, &OrthancSlicesLoader::ParseSliceImagePng), + new OrthancStone::Callable<OrthancSlicesLoader, + IWebService::HttpRequestErrorMessage> + (*this, &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage( + static_cast<unsigned int>(index), slice, SliceImageQuality_FullPng)); +} + + void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice, + size_t index) + { + std::string uri = + ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + + boost::lexical_cast<std::string>(slice.GetFrame())); + + switch (slice.GetConverter().GetExpectedPixelFormat()) + { + case Orthanc::PixelFormat_RGB24: + uri += "/preview"; + break; + + case Orthanc::PixelFormat_Grayscale16: + uri += "/image-uint16"; + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + uri += "/image-int16"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + orthanc_.GetBinaryAsync(uri, "image/x-portable-arbitrarymap", + new OrthancStone::Callable<OrthancSlicesLoader, + OrthancApiClient::BinaryResponseReadyMessage> + (*this, &OrthancSlicesLoader::ParseSliceImagePam), + new OrthancStone::Callable<OrthancSlicesLoader, + IWebService::HttpRequestErrorMessage> + (*this, &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage(static_cast<unsigned int>(index), + slice, SliceImageQuality_FullPam)); + } + + + + void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice, + size_t index, + SliceImageQuality quality) + { + unsigned int value; + + switch (quality) + { + case SliceImageQuality_Jpeg50: + value = 50; + break; + + case SliceImageQuality_Jpeg90: + value = 90; + break; + + case SliceImageQuality_Jpeg95: + value = 95; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + // This requires the official Web viewer plugin to be installed! + std::string uri = ("/web-viewer/instances/jpeg" + + boost::lexical_cast<std::string>(value) + + "-" + slice.GetOrthancInstanceId() + "_" + + boost::lexical_cast<std::string>(slice.GetFrame())); + + orthanc_.GetJsonAsync(uri, + new OrthancStone::Callable<OrthancSlicesLoader, + OrthancApiClient::JsonResponseReadyMessage> + (*this, &OrthancSlicesLoader::ParseSliceImageJpeg), + new OrthancStone::Callable<OrthancSlicesLoader, + IWebService::HttpRequestErrorMessage> + (*this, &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage( + static_cast<unsigned int>(index), slice, quality)); + } + + + + void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index, + SliceImageQuality quality) + { + if (state_ != State_GeometryReady) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + const Slice& slice = GetSlice(index); + + if (slice.HasOrthancDecoding()) + { + switch (quality) + { + case SliceImageQuality_FullPng: + ScheduleSliceImagePng(slice, index); + break; + case SliceImageQuality_FullPam: + ScheduleSliceImagePam(slice, index); + break; + default: + ScheduleSliceImageJpeg(slice, index, quality); + } + } + else + { + std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + + boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz"); + orthanc_.GetBinaryAsync(uri, IWebService::HttpHeaders(), + new OrthancStone::Callable<OrthancSlicesLoader, + OrthancApiClient::BinaryResponseReadyMessage> + (*this, &OrthancSlicesLoader::ParseSliceRawImage), + new OrthancStone::Callable<OrthancSlicesLoader, + IWebService::HttpRequestErrorMessage> + (*this, &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceRawImage( + static_cast<unsigned int>(index), slice)); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,209 @@ +/** + * 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 "../../Messages/IObservable.h" +#include "../../StoneEnumerations.h" +#include "../../Toolbox/SlicesSorter.h" +#include "IWebService.h" +#include "OrthancApiClient.h" +#include "Slice.h" + +#include <Core/Images/Image.h> + + +namespace Deprecated +{ + class OrthancSlicesLoader : public OrthancStone::IObservable, public OrthancStone::IObserver + { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryReadyMessage, OrthancSlicesLoader); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryErrorMessage, OrthancSlicesLoader); + + + class SliceImageReadyMessage : public OrthancStone::OriginMessage<OrthancSlicesLoader> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + unsigned int sliceIndex_; + const Slice& slice_; + const Orthanc::ImageAccessor& image_; + SliceImageQuality effectiveQuality_; + + public: + SliceImageReadyMessage(const OrthancSlicesLoader& origin, + unsigned int sliceIndex, + const Slice& slice, + const Orthanc::ImageAccessor& image, + SliceImageQuality effectiveQuality) : + OriginMessage(origin), + sliceIndex_(sliceIndex), + slice_(slice), + image_(image), + effectiveQuality_(effectiveQuality) + { + } + + unsigned int GetSliceIndex() const + { + return sliceIndex_; + } + + const Slice& GetSlice() const + { + return slice_; + } + + const Orthanc::ImageAccessor& GetImage() const + { + return image_; + } + + SliceImageQuality GetEffectiveQuality() const + { + return effectiveQuality_; + } + }; + + + class SliceImageErrorMessage : public OrthancStone::OriginMessage<OrthancSlicesLoader> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const Slice& slice_; + unsigned int sliceIndex_; + SliceImageQuality effectiveQuality_; + + public: + SliceImageErrorMessage(const OrthancSlicesLoader& origin, + unsigned int sliceIndex, + const Slice& slice, + SliceImageQuality effectiveQuality) : + OriginMessage(origin), + slice_(slice), + sliceIndex_(sliceIndex), + effectiveQuality_(effectiveQuality) + { + } + unsigned int GetSliceIndex() const + { + return sliceIndex_; + } + + const Slice& GetSlice() const + { + return slice_; + } + + SliceImageQuality GetEffectiveQuality() const + { + return effectiveQuality_; + } + }; + + private: + enum State + { + State_Error, + State_Initialization, + State_LoadingGeometry, + State_GeometryReady + }; + + enum Mode + { + Mode_SeriesGeometry, + Mode_InstanceGeometry, + Mode_FrameGeometry, + Mode_LoadImage, + Mode_LoadRawImage, + Mode_LoadDicomFile + }; + + class Operation; + + OrthancApiClient& orthanc_; + State state_; + OrthancStone::SlicesSorter slices_; + + void NotifySliceImageSuccess(const Operation& operation, + const Orthanc::ImageAccessor& image); + + void NotifySliceImageError(const Operation& operation); + + void OnGeometryError(const IWebService::HttpRequestErrorMessage& message); + + void OnSliceImageError(const IWebService::HttpRequestErrorMessage& message); + + void ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message); + + void ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message); + + void ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message); + + void ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message); + + void ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message); + + void ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message); + + void ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message); + + void ScheduleSliceImagePng(const Slice& slice, + size_t index); + + void ScheduleSliceImagePam(const Slice& slice, + size_t index); + + void ScheduleSliceImageJpeg(const Slice& slice, + size_t index, + SliceImageQuality quality); + + void SortAndFinalizeSlices(); + + public: + OrthancSlicesLoader(OrthancStone::MessageBroker& broker, + //ISliceLoaderObserver& callback, + OrthancApiClient& orthancApi); + + void ScheduleLoadSeries(const std::string& seriesId); + + void ScheduleLoadInstance(const std::string& instanceId); + + void ScheduleLoadFrame(const std::string& instanceId, + unsigned int frame); + + bool IsGeometryReady() const; + + size_t GetSlicesCount() const; + + const Slice& GetSlice(size_t index) const; + + bool LookupSlice(size_t& index, + const OrthancStone::CoordinateSystem3D& plane) const; + + void ScheduleLoadSliceImage(size_t index, + SliceImageQuality requestedQuality); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/ParallelSlices.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,215 @@ +/** + * 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 "ParallelSlices.h" + +#include "../../Toolbox/GeometryToolbox.h" +#include "../../Volumes/ImageBuffer3D.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +namespace Deprecated +{ + ParallelSlices::ParallelSlices() + { + Clear(); + } + + + ParallelSlices::ParallelSlices(const ParallelSlices& other) + { + normal_ = other.normal_; + + slices_.resize(other.slices_.size()); + + for (size_t i = 0; i < slices_.size(); i++) + { + assert(other.slices_[i] != NULL); + slices_[i] = new OrthancStone::CoordinateSystem3D(*other.slices_[i]); + } + } + + + void ParallelSlices::Clear() + { + for (size_t i = 0; i < slices_.size(); i++) + { + if (slices_[i] != NULL) + { + delete slices_[i]; + slices_[i] = NULL; + } + } + + slices_.clear(); + OrthancStone::LinearAlgebra::AssignVector(normal_, 0, 0, 1); + } + + + ParallelSlices::~ParallelSlices() + { + Clear(); + } + + + void ParallelSlices::AddSlice(const OrthancStone::CoordinateSystem3D& slice) + { + if (slices_.empty()) + { + normal_ = slice.GetNormal(); + slices_.push_back(new OrthancStone::CoordinateSystem3D(slice)); + } + else if (OrthancStone::GeometryToolbox::IsParallel(slice.GetNormal(), normal_)) + { + slices_.push_back(new OrthancStone::CoordinateSystem3D(slice)); + } + else + { + LOG(ERROR) << "Trying to add a slice that is not parallel to the previous ones"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void ParallelSlices::AddSlice(const OrthancStone::Vector& origin, + const OrthancStone::Vector& axisX, + const OrthancStone::Vector& axisY) + { + OrthancStone::CoordinateSystem3D slice(origin, axisX, axisY); + AddSlice(slice); + } + + + const OrthancStone::CoordinateSystem3D& ParallelSlices::GetSlice(size_t index) const + { + if (index >= slices_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + return *slices_[index]; + } + } + + + bool ParallelSlices::ComputeClosestSlice(size_t& closestSlice, + double& closestDistance, + const OrthancStone::Vector& origin) const + { + if (slices_.empty()) + { + return false; + } + + double reference = boost::numeric::ublas::inner_prod(origin, normal_); + + closestSlice = 0; + closestDistance = std::numeric_limits<double>::infinity(); + + for (size_t i = 0; i < slices_.size(); i++) + { + double distance = fabs(boost::numeric::ublas::inner_prod(slices_[i]->GetOrigin(), normal_) - reference); + + if (distance < closestDistance) + { + closestSlice = i; + closestDistance = distance; + } + } + + return true; + } + + + ParallelSlices* ParallelSlices::Reverse() const + { + std::auto_ptr<ParallelSlices> reversed(new ParallelSlices); + + for (size_t i = slices_.size(); i > 0; i--) + { + const OrthancStone::CoordinateSystem3D& slice = *slices_[i - 1]; + + reversed->AddSlice(slice.GetOrigin(), + -slice.GetAxisX(), + slice.GetAxisY()); + } + + return reversed.release(); + } + + + ParallelSlices* ParallelSlices::FromVolumeImage(const OrthancStone::VolumeImageGeometry& geometry, + OrthancStone::VolumeProjection projection) + { + const OrthancStone::Vector dimensions = geometry.GetVoxelDimensions(OrthancStone::VolumeProjection_Axial); + const OrthancStone::CoordinateSystem3D& axial = geometry.GetAxialGeometry(); + + std::auto_ptr<ParallelSlices> result(new ParallelSlices); + + switch (projection) + { + case OrthancStone::VolumeProjection_Axial: + for (unsigned int z = 0; z < geometry.GetDepth(); z++) + { + OrthancStone::Vector origin = axial.GetOrigin(); + origin += static_cast<double>(z) * dimensions[2] * axial.GetNormal(); + + result->AddSlice(origin, + axial.GetAxisX(), + axial.GetAxisY()); + } + break; + + case OrthancStone::VolumeProjection_Coronal: + for (unsigned int y = 0; y < geometry.GetHeight(); y++) + { + OrthancStone::Vector origin = axial.GetOrigin(); + origin += static_cast<double>(y) * dimensions[1] * axial.GetAxisY(); + origin += static_cast<double>(geometry.GetDepth() - 1) * dimensions[2] * axial.GetNormal(); + + result->AddSlice(origin, + axial.GetAxisX(), + -axial.GetNormal()); + } + break; + + case OrthancStone::VolumeProjection_Sagittal: + for (unsigned int x = 0; x < geometry.GetWidth(); x++) + { + OrthancStone::Vector origin = axial.GetOrigin(); + origin += static_cast<double>(x) * dimensions[0] * axial.GetAxisX(); + origin += static_cast<double>(geometry.GetDepth() - 1) * dimensions[2] * axial.GetNormal(); + + result->AddSlice(origin, + axial.GetAxisY(), + -axial.GetNormal()); + } + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + return result.release(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/ParallelSlices.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,73 @@ +/** + * 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/CoordinateSystem3D.h" +#include "../../Volumes/VolumeImageGeometry.h" + +namespace Deprecated +{ + class ParallelSlices : public boost::noncopyable + { + private: + OrthancStone::Vector normal_; + std::vector<OrthancStone::CoordinateSystem3D*> slices_; + + ParallelSlices& operator= (const ParallelSlices& other); // Forbidden + + void Clear(); + + public: + ParallelSlices(); + + ParallelSlices(const ParallelSlices& other); + + ~ParallelSlices(); + + const OrthancStone::Vector& GetNormal() const + { + return normal_; + } + + void AddSlice(const OrthancStone::CoordinateSystem3D& slice); + + void AddSlice(const OrthancStone::Vector& origin, + const OrthancStone::Vector& axisX, + const OrthancStone::Vector& axisY); + + size_t GetSliceCount() const + { + return slices_.size(); + } + + const OrthancStone::CoordinateSystem3D& GetSlice(size_t index) const; + + bool ComputeClosestSlice(size_t& closestSlice, + double& closestDistance, + const OrthancStone::Vector& origin) const; + + ParallelSlices* Reverse() const; + + static ParallelSlices* FromVolumeImage(const OrthancStone::VolumeImageGeometry& geometry, + OrthancStone::VolumeProjection projection); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/ParallelSlicesCursor.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,221 @@ +/** + * 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 "ParallelSlicesCursor.h" + +#include <Core/OrthancException.h> + +namespace Deprecated +{ + size_t ParallelSlicesCursor::GetDefaultSlice() + { + if (slices_.get() == NULL) + { + return 0; + } + else + { + return slices_->GetSliceCount() / 2; + } + } + + + size_t ParallelSlicesCursor::GetSliceCount() + { + if (slices_.get() == NULL) + { + return 0; + } + else + { + return slices_->GetSliceCount(); + } + } + + + OrthancStone::CoordinateSystem3D ParallelSlicesCursor::GetSlice(size_t slice) + { + if (slices_.get() == NULL) + { + return OrthancStone::CoordinateSystem3D(); + } + else + { + return slices_->GetSlice(slice); + } + } + + + void ParallelSlicesCursor::SetGeometry(const ParallelSlices& slices) + { + slices_.reset(new ParallelSlices(slices)); + + currentSlice_ = GetDefaultSlice(); + } + + + OrthancStone::CoordinateSystem3D ParallelSlicesCursor::GetCurrentSlice() + { + if (slices_.get() != NULL && + currentSlice_ < slices_->GetSliceCount()) + { + return slices_->GetSlice(currentSlice_); + } + else + { + return OrthancStone::CoordinateSystem3D(); // No slice is available, return the canonical geometry + } + } + + + bool ParallelSlicesCursor::SetDefaultSlice() + { + size_t slice = GetDefaultSlice(); + + if (currentSlice_ != slice) + { + currentSlice_ = slice; + return true; + } + else + { + return false; + } + } + + + bool ParallelSlicesCursor::ApplyOffset(OrthancStone::SliceOffsetMode mode, + int offset) + { + if (slices_.get() == NULL) + { + return false; + } + + int count = static_cast<int>(slices_->GetSliceCount()); + if (count == 0) + { + return false; + } + + int slice; + if (static_cast<int>(currentSlice_) >= count) + { + slice = count - 1; + } + else + { + slice = static_cast<int>(currentSlice_); + } + + switch (mode) + { + case OrthancStone::SliceOffsetMode_Absolute: + { + slice = offset; + break; + } + + case OrthancStone::SliceOffsetMode_Relative: + { + slice += offset; + break; + } + + case OrthancStone::SliceOffsetMode_Loop: + { + slice += offset; + while (slice < 0) + { + slice += count; + } + + while (slice >= count) + { + slice -= count; + } + + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (slice < 0) + { + slice = 0; + } + + if (slice >= count) + { + slice = count - 1; + } + + if (slice != static_cast<int>(currentSlice_)) + { + currentSlice_ = static_cast<int>(slice); + return true; + } + else + { + return false; + } + } + + + bool ParallelSlicesCursor::ApplyWheelEvent(OrthancStone::MouseWheelDirection direction, + OrthancStone::KeyboardModifiers modifiers) + { + int offset = (modifiers & OrthancStone::KeyboardModifiers_Control ? 10 : 1); + + switch (direction) + { + case OrthancStone::MouseWheelDirection_Down: + return ApplyOffset(OrthancStone::SliceOffsetMode_Relative, -offset); + + case OrthancStone::MouseWheelDirection_Up: + return ApplyOffset(OrthancStone::SliceOffsetMode_Relative, offset); + + default: + return false; + } + } + + + bool ParallelSlicesCursor::LookupSliceContainingPoint(const OrthancStone::Vector& p) + { + size_t slice; + double distance; + + if (slices_.get() != NULL && + slices_->ComputeClosestSlice(slice, distance, p)) + { + if (currentSlice_ != slice) + { + currentSlice_ = slice; + return true; + } + } + + return false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/ParallelSlicesCursor.h Mon Jun 24 14:35: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 "ParallelSlices.h" +#include "../../StoneEnumerations.h" + +namespace Deprecated +{ + class ParallelSlicesCursor : public boost::noncopyable + { + private: + std::auto_ptr<ParallelSlices> slices_; + size_t currentSlice_; + + size_t GetDefaultSlice(); + + public: + ParallelSlicesCursor() : + currentSlice_(0) + { + } + + void SetGeometry(const ParallelSlices& slices); + + size_t GetSliceCount(); + + OrthancStone::CoordinateSystem3D GetSlice(size_t slice); + + OrthancStone::CoordinateSystem3D GetCurrentSlice(); + + // Returns "true" iff. the slice has actually changed + bool SetDefaultSlice(); + + // Returns "true" iff. the slice has actually changed + bool ApplyOffset(OrthancStone::SliceOffsetMode mode, + int offset); + + // Returns "true" iff. the slice has actually changed + bool ApplyWheelEvent(OrthancStone::MouseWheelDirection direction, + OrthancStone::KeyboardModifiers modifiers); + + // Returns "true" iff. the slice has actually changed + bool LookupSliceContainingPoint(const OrthancStone::Vector& p); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/Slice.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,367 @@ +/** + * 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 "Slice.h" + +#include "../../StoneEnumerations.h" +#include "../../Toolbox/GeometryToolbox.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +#include <boost/lexical_cast.hpp> + +namespace Deprecated +{ + static bool ParseDouble(double& target, + const std::string& source) + { + try + { + target = boost::lexical_cast<double>(source); + return true; + } + catch (boost::bad_lexical_cast&) + { + return false; + } + } + + Slice* Slice::Clone() const + { + std::auto_ptr<Slice> target(new Slice()); + + target->type_ = type_; + target->orthancInstanceId_ = orthancInstanceId_; + target->sopClassUid_ = sopClassUid_; + target->frame_ = frame_; + target->frameCount_ = frameCount_; + target->geometry_ = geometry_; + target->pixelSpacingX_ = pixelSpacingX_; + target->pixelSpacingY_ = pixelSpacingY_; + target->thickness_ = thickness_; + target->width_ = width_; + target->height_ = height_; + target->converter_ = converter_; + if (imageInformation_.get() != NULL) + target->imageInformation_.reset(imageInformation_->Clone()); + + return target.release(); + } + + bool Slice::ComputeRTDoseGeometry(const Orthanc::DicomMap& dataset, + unsigned int frame) + { + // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html + + { + std::string increment; + + if (dataset.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) + { + Orthanc::Toolbox::ToUpperCase(increment); + if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag + { + LOG(ERROR) << "Bad value for the \"FrameIncrementPointer\" tag"; + return false; + } + } + } + + std::string offsetTag; + + if (!dataset.CopyToString(offsetTag, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, false) || + offsetTag.empty()) + { + LOG(ERROR) << "Cannot read the \"GridFrameOffsetVector\" tag, check you are using Orthanc >= 1.3.1"; + return false; + } + + std::vector<std::string> offsets; + Orthanc::Toolbox::TokenizeString(offsets, offsetTag, '\\'); + + if (frameCount_ <= 1 || + offsets.size() < frameCount_ || + offsets.size() < 2 || + frame >= frameCount_) + { + LOG(ERROR) << "No information about the 3D location of some slice(s) in a RT DOSE"; + return false; + } + + double offset0, offset1, z; + + if (!ParseDouble(offset0, offsets[0]) || + !ParseDouble(offset1, offsets[1]) || + !ParseDouble(z, offsets[frame])) + { + LOG(ERROR) << "Invalid syntax"; + return false; + } + + if (!OrthancStone::LinearAlgebra::IsCloseToZero(offset0)) + { + LOG(ERROR) << "Invalid syntax"; + return false; + } + + geometry_ = OrthancStone::CoordinateSystem3D(geometry_.GetOrigin() + z * geometry_.GetNormal(), + //+ 650 * geometry_.GetAxisX(), + geometry_.GetAxisX(), + geometry_.GetAxisY()); + + thickness_ = offset1 - offset0; + if (thickness_ < 0) + { + thickness_ = -thickness_; + } + + return true; + } + + + bool Slice::ParseOrthancFrame(const Orthanc::DicomMap& dataset, + const std::string& instanceId, + unsigned int frame) + { + orthancInstanceId_ = instanceId; + frame_ = frame; + type_ = Type_OrthancDecodableFrame; + imageInformation_.reset(new Orthanc::DicomImageInformation(dataset)); + + if (!dataset.CopyToString(sopClassUid_, Orthanc::DICOM_TAG_SOP_CLASS_UID, false) || + sopClassUid_.empty()) + { + LOG(ERROR) << "Instance without a SOP class UID"; + return false; + } + + if (!dataset.ParseUnsignedInteger32(frameCount_, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) + { + frameCount_ = 1; // Assume instance with one frame + } + + if (frame >= frameCount_) + { + return false; + } + + if (!dataset.ParseUnsignedInteger32(width_, Orthanc::DICOM_TAG_COLUMNS) || + !dataset.ParseUnsignedInteger32(height_, Orthanc::DICOM_TAG_ROWS)) + { + return false; + } + + thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); + + std::string tmp; + if (dataset.CopyToString(tmp, Orthanc::DICOM_TAG_SLICE_THICKNESS, false)) + { + if (!tmp.empty() && + !ParseDouble(thickness_, tmp)) + { + return false; // Syntax error + } + } + + converter_.ReadParameters(dataset); + + OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dataset); + + std::string position, orientation; + if (dataset.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && + dataset.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) + { + geometry_ = OrthancStone::CoordinateSystem3D(position, orientation); + + bool ok = true; + + switch (OrthancStone::StringToSopClassUid(sopClassUid_)) + { + case OrthancStone::SopClassUid_RTDose: + type_ = Type_OrthancRawFrame; + ok = ComputeRTDoseGeometry(dataset, frame); + break; + + default: + break; + } + + if (!ok) + { + LOG(ERROR) << "Cannot deduce the 3D location of frame " << frame + << " in instance " << instanceId << ", whose SOP class UID is: " << sopClassUid_; + return false; + } + } + + return true; + } + + + const std::string Slice::GetOrthancInstanceId() const + { + if (type_ == Type_OrthancDecodableFrame || + type_ == Type_OrthancRawFrame) + { + return orthancInstanceId_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + unsigned int Slice::GetFrame() const + { + if (type_ == Type_Invalid) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return frame_; + } + + + const OrthancStone::CoordinateSystem3D& Slice::GetGeometry() const + { + if (type_ == Type_Invalid) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return geometry_; + } + + + double Slice::GetThickness() const + { + if (type_ == Type_Invalid) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return thickness_; + } + + + double Slice::GetPixelSpacingX() const + { + if (type_ == Type_Invalid) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return pixelSpacingX_; + } + + + double Slice::GetPixelSpacingY() const + { + if (type_ == Type_Invalid) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return pixelSpacingY_; + } + + + unsigned int Slice::GetWidth() const + { + if (type_ == Type_Invalid) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return width_; + } + + + unsigned int Slice::GetHeight() const + { + if (type_ == Type_Invalid) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return height_; + } + + + const DicomFrameConverter& Slice::GetConverter() const + { + if (type_ == Type_Invalid) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return converter_; + } + + + bool Slice::ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const + { + if (type_ == Type_Invalid) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + bool opposite; + return (OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, + GetGeometry().GetNormal(), + plane.GetNormal()) && + OrthancStone::LinearAlgebra::IsNear(GetGeometry().ProjectAlongNormal(GetGeometry().GetOrigin()), + GetGeometry().ProjectAlongNormal(plane.GetOrigin()), + thickness_ / 2.0)); + } + + + void Slice::GetExtent(std::vector<OrthancStone::Vector>& points) const + { + double sx = GetPixelSpacingX(); + double sy = GetPixelSpacingY(); + double w = static_cast<double>(GetWidth()); + double h = static_cast<double>(GetHeight()); + + points.clear(); + points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5 * sx, -0.5 * sy)); + points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, -0.5 * sy)); + points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5 * sx, (h - 0.5) * sy)); + points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, (h - 0.5) * sy)); + } + + + const Orthanc::DicomImageInformation& Slice::GetImageInformation() const + { + if (imageInformation_.get() == NULL) + { + // Only available if constructing the "Slice" object with a DICOM map + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *imageInformation_; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/Slice.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,155 @@ +/** + * 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/CoordinateSystem3D.h" +#include "DicomFrameConverter.h" + +#include <Core/DicomFormat/DicomImageInformation.h> +#include <Core/IDynamicObject.h> + +namespace Deprecated +{ + // TODO - Remove this class + class Slice : + public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */ + { + private: + enum Type + { + Type_Invalid, + Type_Standalone, + Type_OrthancDecodableFrame, + Type_OrthancRawFrame + // TODO A slice could come from some DICOM file (URL) + }; + + bool ComputeRTDoseGeometry(const Orthanc::DicomMap& dataset, + unsigned int frame); + + Type type_; + std::string orthancInstanceId_; + std::string sopClassUid_; + unsigned int frame_; + unsigned int frameCount_; // TODO : Redundant with "imageInformation_" + OrthancStone::CoordinateSystem3D geometry_; + double pixelSpacingX_; + double pixelSpacingY_; + double thickness_; + unsigned int width_; // TODO : Redundant with "imageInformation_" + unsigned int height_; // TODO : Redundant with "imageInformation_" + DicomFrameConverter converter_; // TODO : Partially redundant with "imageInformation_" + + std::auto_ptr<Orthanc::DicomImageInformation> imageInformation_; + + public: + Slice() : + type_(Type_Invalid) + { + } + + + // this constructor is used to reference, i.e, a slice that is being loaded + Slice(const std::string& orthancInstanceId, + unsigned int frame) : + type_(Type_Invalid), + orthancInstanceId_(orthancInstanceId), + frame_(frame) + { + } + + // TODO Is this constructor the best way to go to tackle missing + // layers within SliceViewerWidget? + Slice(const OrthancStone::CoordinateSystem3D& plane, + double thickness) : + type_(Type_Standalone), + frame_(0), + frameCount_(0), + geometry_(plane), + pixelSpacingX_(1), + pixelSpacingY_(1), + thickness_(thickness), + width_(0), + height_(0) + { + } + + Slice(const OrthancStone::CoordinateSystem3D& plane, + double pixelSpacingX, + double pixelSpacingY, + double thickness, + unsigned int width, + unsigned int height, + const DicomFrameConverter& converter) : + type_(Type_Standalone), + frameCount_(1), + geometry_(plane), + pixelSpacingX_(pixelSpacingX), + pixelSpacingY_(pixelSpacingY), + thickness_(thickness), + width_(width), + height_(height), + converter_(converter) + { + } + + bool IsValid() const + { + return type_ != Type_Invalid; + } + + bool ParseOrthancFrame(const Orthanc::DicomMap& dataset, + const std::string& instanceId, + unsigned int frame); + + bool HasOrthancDecoding() const + { + return type_ == Type_OrthancDecodableFrame; + } + + const std::string GetOrthancInstanceId() const; + + unsigned int GetFrame() const; + + const OrthancStone::CoordinateSystem3D& GetGeometry() const; + + double GetThickness() const; + + double GetPixelSpacingX() const; + + double GetPixelSpacingY() const; + + unsigned int GetWidth() const; + + unsigned int GetHeight() const; + + const DicomFrameConverter& GetConverter() const; + + bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const; + + void GetExtent(std::vector<OrthancStone::Vector>& points) const; + + const Orthanc::DicomImageInformation& GetImageInformation() const; + + Slice* Clone() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/ViewportGeometry.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,217 @@ +/** + * 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 "ViewportGeometry.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +#include <boost/math/special_functions/round.hpp> + +namespace Deprecated +{ + void ViewportGeometry::ComputeTransform() + { + // The following lines must be read in reverse order! + cairo_matrix_t tmp; + + // Bring the center of the scene to the center of the view + cairo_matrix_init_translate(&transform_, + panX_ + static_cast<double>(width_) / 2.0, + panY_ + static_cast<double>(height_) / 2.0); + + // Apply the zoom around (0,0) + cairo_matrix_init_scale(&tmp, zoom_, zoom_); + cairo_matrix_multiply(&transform_, &tmp, &transform_); + + // Bring the center of the scene to (0,0) + cairo_matrix_init_translate(&tmp, + -(sceneExtent_.GetX1() + sceneExtent_.GetX2()) / 2.0, + -(sceneExtent_.GetY1() + sceneExtent_.GetY2()) / 2.0); + cairo_matrix_multiply(&transform_, &tmp, &transform_); + } + + + ViewportGeometry::ViewportGeometry() + { + width_ = 0; + height_ = 0; + + zoom_ = 1; + panX_ = 0; + panY_ = 0; + + ComputeTransform(); + } + + + void ViewportGeometry::SetDisplaySize(unsigned int width, + unsigned int height) + { + if (width_ != width || + height_ != height) + { + LOG(INFO) << "New display size: " << width << "x" << height; + + width_ = width; + height_ = height; + + ComputeTransform(); + } + } + + + void ViewportGeometry::SetSceneExtent(const OrthancStone::Extent2D& extent) + { + LOG(INFO) << "New scene extent: (" + << extent.GetX1() << "," << extent.GetY1() << ") => (" + << extent.GetX2() << "," << extent.GetY2() << ")"; + + sceneExtent_ = extent; + ComputeTransform(); + } + + + void ViewportGeometry::MapDisplayToScene(double& sceneX /* out */, + double& sceneY /* out */, + double x, + double y) const + { + cairo_matrix_t transform = transform_; + + if (cairo_matrix_invert(&transform) != CAIRO_STATUS_SUCCESS) + { + LOG(ERROR) << "Cannot invert singular matrix"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + sceneX = x; + sceneY = y; + cairo_matrix_transform_point(&transform, &sceneX, &sceneY); + } + + + void ViewportGeometry::MapSceneToDisplay(int& displayX /* out */, + int& displayY /* out */, + double x, + double y) const + { + cairo_matrix_transform_point(&transform_, &x, &y); + + displayX = static_cast<int>(boost::math::iround(x)); + displayY = static_cast<int>(boost::math::iround(y)); + } + + + 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, + static_cast<int>(displayTouches[t].x), + static_cast<int>(displayTouches[t].y)); + + sceneTouches.push_back(Touch((float)sceneX, (float)sceneY)); + } + } + + void ViewportGeometry::MapPixelCenterToScene(double& sceneX, + double& sceneY, + int x, + int y) const + { + // Take the center of the pixel + MapDisplayToScene(sceneX, sceneY, + static_cast<double>(x) + 0.5, + static_cast<double>(y) + 0.5); + } + + + void ViewportGeometry::FitContent() + { + if (width_ > 0 && + height_ > 0 && + !sceneExtent_.IsEmpty()) + { + double zoomX = static_cast<double>(width_) / (sceneExtent_.GetX2() - sceneExtent_.GetX1()); + double zoomY = static_cast<double>(height_) / (sceneExtent_.GetY2() - sceneExtent_.GetY1()); + zoom_ = zoomX < zoomY ? zoomX : zoomY; + + panX_ = 0; + panY_ = 0; + + ComputeTransform(); + } + } + + + void ViewportGeometry::ApplyTransform(OrthancStone::CairoContext& context) const + { + cairo_set_matrix(context.GetObject(), &transform_); + } + + + void ViewportGeometry::GetPan(double& x, + double& y) const + { + x = panX_; + y = panY_; + } + + + void ViewportGeometry::SetPan(double x, + double y) + { + panX_ = x; + panY_ = y; + ComputeTransform(); + } + + + void ViewportGeometry::SetZoom(double zoom) + { + zoom_ = zoom; + ComputeTransform(); + } + + + OrthancStone::Matrix ViewportGeometry::GetMatrix() const + { + OrthancStone::Matrix m(3, 3); + + m(0, 0) = transform_.xx; + m(0, 1) = transform_.xy; + m(0, 2) = transform_.x0; + m(1, 0) = transform_.yx; + m(1, 1) = transform_.yy; + m(1, 2) = transform_.y0; + m(2, 0) = 0; + m(2, 1) = 0; + m(2, 2) = 1; + + return m; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/ViewportGeometry.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,110 @@ +/** + * 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 "../../Wrappers/CairoContext.h" +#include "../../Toolbox/Extent2D.h" +#include "../../Toolbox/LinearAlgebra.h" +#include "../Viewport/IMouseTracker.h" // to include "Touch" definition + +namespace Deprecated +{ + class ViewportGeometry + { + private: + // Extent of the scene (in world units) + OrthancStone::Extent2D sceneExtent_; + + // Size of the display (in pixels) + unsigned int width_; + unsigned int height_; + + // Zoom/pan + double zoom_; + double panX_; // In pixels (display units) + double panY_; + + cairo_matrix_t transform_; // Scene-to-display transformation + + void ComputeTransform(); + + public: + ViewportGeometry(); + + void SetDisplaySize(unsigned int width, + unsigned int height); + + void SetSceneExtent(const OrthancStone::Extent2D& extent); + + const OrthancStone::Extent2D& GetSceneExtent() const + { + return sceneExtent_; + } + + void MapDisplayToScene(double& sceneX /* out */, + double& sceneY /* out */, + double x, + double y) const; + + void MapPixelCenterToScene(double& sceneX /* out */, + double& sceneY /* out */, + 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, + double y) const; + + unsigned int GetDisplayWidth() const + { + return width_; + } + + unsigned int GetDisplayHeight() const + { + return height_; + } + + double GetZoom() const + { + return zoom_; + } + + void FitContent(); + + void ApplyTransform(OrthancStone::CairoContext& context) const; + + void GetPan(double& x, + double& y) const; + + void SetPan(double x, + double y); + + void SetZoom(double zoom); + + OrthancStone::Matrix GetMatrix() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Viewport/CairoFont.cpp Mon Jun 24 14:35: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/>. + **/ + + +#include "CairoFont.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +namespace Deprecated +{ + CairoFont::CairoFont(const char* family, + cairo_font_slant_t slant, + cairo_font_weight_t weight) + { + font_ = cairo_toy_font_face_create(family, slant, weight); + if (font_ == NULL) + { + LOG(ERROR) << "Unknown font: " << family; + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + } + + + CairoFont::~CairoFont() + { + if (font_ != NULL) + { + cairo_font_face_destroy(font_); + } + } + + + void CairoFont::Draw(OrthancStone::CairoContext& context, + const std::string& text, + double size) + { + if (size <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + cairo_t* cr = context.GetObject(); + cairo_set_font_face(cr, font_); + cairo_set_font_size(cr, size); + cairo_show_text(cr, text.c_str()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Viewport/CairoFont.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,52 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 + +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if ORTHANC_SANDBOXED == 1 +# error The class CairoFont cannot be used in sandboxed environments +#endif + +#include "../../Wrappers/CairoContext.h" + +namespace Deprecated +{ + class CairoFont : public boost::noncopyable + { + private: + cairo_font_face_t* font_; + + public: + CairoFont(const char* family, + cairo_font_slant_t slant, + cairo_font_weight_t weight); + + ~CairoFont(); + + void Draw(OrthancStone::CairoContext& context, + const std::string& text, + double size); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Viewport/IMouseTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,66 @@ +/** + * 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 "../../Wrappers/CairoSurface.h" +#include <vector> + +namespace Deprecated +{ + 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. + class IMouseTracker : public boost::noncopyable + { + public: + virtual ~IMouseTracker() + { + } + + virtual void Render(Orthanc::ImageAccessor& surface) = 0; + + virtual void MouseUp() = 0; + + // Returns "true" iff. the background scene must be repainted + virtual void MouseMove(int x, + int y, + const std::vector<Touch>& displayTouches) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Viewport/IStatusBar.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,40 @@ +/** + * 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 <string> +#include <boost/noncopyable.hpp> + +namespace Deprecated +{ + class IStatusBar : public boost::noncopyable + { + public: + virtual ~IStatusBar() + { + } + + virtual void ClearMessage() = 0; + + virtual void SetMessage(const std::string& message) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Viewport/IViewport.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,95 @@ +/** + * 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 "IStatusBar.h" +#include "../../StoneEnumerations.h" +#include "../../Messages/IObservable.h" + +#include <Core/Images/ImageAccessor.h> +#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition + +namespace Deprecated +{ + class IWidget; // Forward declaration + + class IViewport : public OrthancStone::IObservable + { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ViewportChangedMessage, IViewport); + + IViewport(OrthancStone::MessageBroker& broker) : + IObservable(broker) + { + } + + virtual ~IViewport() + { + } + + virtual void FitContent() = 0; + + virtual void SetStatusBar(IStatusBar& statusBar) = 0; + + virtual void SetSize(unsigned int width, + unsigned int height) = 0; + + // The function returns "true" iff. a new frame was rendered + virtual bool Render(Orthanc::ImageAccessor& surface) = 0; + + virtual void MouseDown(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& touches) = 0; + + virtual void MouseUp() = 0; + + virtual void MouseMove(int x, + int y, + const std::vector<Touch>& displayTouches) = 0; + + virtual void MouseEnter() = 0; + + virtual void MouseLeave() = 0; + + virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers) = 0; + + virtual void KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers) = 0; + + virtual bool HasAnimation() = 0; + + virtual void DoAnimation() = 0; + + // Should only be called from IWidget + // TODO Why should this be virtual? + virtual void NotifyContentChanged() + { + BroadcastMessage(ViewportChangedMessage(*this)); + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Viewport/WidgetViewport.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,289 @@ +/** + * 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 "WidgetViewport.h" + +#include <Core/Images/ImageProcessing.h> +#include <Core/OrthancException.h> + +namespace Deprecated +{ + WidgetViewport::WidgetViewport(OrthancStone::MessageBroker& broker) : + IViewport(broker), + statusBar_(NULL), + isMouseOver_(false), + lastMouseX_(0), + lastMouseY_(0), + backgroundChanged_(false) + { + } + + + void WidgetViewport::FitContent() + { + if (centralWidget_.get() != NULL) + { + centralWidget_->FitContent(); + } + } + + + void WidgetViewport::SetStatusBar(IStatusBar& statusBar) + { + statusBar_ = &statusBar; + + if (centralWidget_.get() != NULL) + { + centralWidget_->SetStatusBar(statusBar); + } + } + + + IWidget& WidgetViewport::SetCentralWidget(IWidget* widget) + { + if (widget == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + mouseTracker_.reset(NULL); + + centralWidget_.reset(widget); + centralWidget_->SetViewport(*this); + + if (statusBar_ != NULL) + { + centralWidget_->SetStatusBar(*statusBar_); + } + + NotifyBackgroundChanged(); + + return *widget; + } + + + void WidgetViewport::NotifyBackgroundChanged() + { + backgroundChanged_ = true; + NotifyContentChanged(); + } + + + void WidgetViewport::SetSize(unsigned int width, + unsigned int height) + { + background_.SetSize(width, height, false /* no alpha */); + + if (centralWidget_.get() != NULL) + { + centralWidget_->SetSize(width, height); + } + + NotifyBackgroundChanged(); + } + + + bool WidgetViewport::Render(Orthanc::ImageAccessor& surface) + { + if (centralWidget_.get() == NULL) + { + return false; + } + + Orthanc::ImageAccessor background; + background_.GetWriteableAccessor(background); + + if (backgroundChanged_ && + !centralWidget_->Render(background)) + { + return false; + } + + if (background.GetWidth() != surface.GetWidth() || + background.GetHeight() != surface.GetHeight()) + { + return false; + } + + Orthanc::ImageProcessing::Convert(surface, background); + + if (mouseTracker_.get() != NULL) + { + mouseTracker_->Render(surface); + } + else if (isMouseOver_) + { + centralWidget_->RenderMouseOver(surface, lastMouseX_, lastMouseY_); + } + + return true; + } + + void WidgetViewport::TouchStart(const std::vector<Touch>& displayTouches) + { + MouseDown(OrthancStone::MouseButton_Left, (int)displayTouches[0].x, (int)displayTouches[0].y, OrthancStone::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(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& displayTouches + ) + { + lastMouseX_ = x; + lastMouseY_ = y; + + if (centralWidget_.get() != NULL) + { + mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers, displayTouches)); + } + else + { + mouseTracker_.reset(NULL); + } + + NotifyContentChanged(); + } + + + void WidgetViewport::MouseUp() + { + if (mouseTracker_.get() != NULL) + { + mouseTracker_->MouseUp(); + mouseTracker_.reset(NULL); + NotifyContentChanged(); + } + } + + + void WidgetViewport::MouseMove(int x, + int y, + const std::vector<Touch>& displayTouches) + { + if (centralWidget_.get() == NULL) + { + return; + } + + lastMouseX_ = x; + lastMouseY_ = y; + + bool repaint = false; + + if (mouseTracker_.get() != NULL) + { + mouseTracker_->MouseMove(x, y, displayTouches); + repaint = true; + } + else + { + repaint = centralWidget_->HasRenderMouseOver(); + } + + if (repaint) + { + // The scene must be repainted, notify the observers + NotifyContentChanged(); + } + } + + + void WidgetViewport::MouseEnter() + { + isMouseOver_ = true; + NotifyContentChanged(); + } + + + void WidgetViewport::MouseLeave() + { + isMouseOver_ = false; + + if (mouseTracker_.get() != NULL) + { + mouseTracker_->MouseUp(); + mouseTracker_.reset(NULL); + } + + NotifyContentChanged(); + } + + + void WidgetViewport::MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers) + { + if (centralWidget_.get() != NULL && + mouseTracker_.get() == NULL) + { + centralWidget_->MouseWheel(direction, x, y, modifiers); + } + } + + + void WidgetViewport::KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers) + { + if (centralWidget_.get() != NULL && + mouseTracker_.get() == NULL) + { + centralWidget_->KeyPressed(key, keyChar, modifiers); + } + } + + + bool WidgetViewport::HasAnimation() + { + if (centralWidget_.get() != NULL) + { + return centralWidget_->HasAnimation(); + } + else + { + return false; + } + } + + + void WidgetViewport::DoAnimation() + { + if (centralWidget_.get() != NULL) + { + centralWidget_->DoAnimation(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Viewport/WidgetViewport.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,94 @@ +/** + * 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 "IViewport.h" +#include "../Widgets/IWidget.h" + +#include <memory> + +namespace Deprecated +{ + class WidgetViewport : public IViewport + { + private: + std::auto_ptr<IWidget> centralWidget_; + IStatusBar* statusBar_; + std::auto_ptr<IMouseTracker> mouseTracker_; + bool isMouseOver_; + int lastMouseX_; + int lastMouseY_; + OrthancStone::CairoSurface background_; + bool backgroundChanged_; + + public: + WidgetViewport(OrthancStone::MessageBroker& broker); + + virtual void FitContent(); + + virtual void SetStatusBar(IStatusBar& statusBar); + + IWidget& SetCentralWidget(IWidget* widget); // Takes ownership + + virtual void NotifyBackgroundChanged(); + + virtual void SetSize(unsigned int width, + unsigned int height); + + virtual bool Render(Orthanc::ImageAccessor& surface); + + virtual void MouseDown(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& displayTouches); + + virtual void MouseUp(); + + virtual void MouseMove(int x, + 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(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers); + + virtual void KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers); + + virtual bool HasAnimation(); + + virtual void DoAnimation(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Volumes/ISlicedVolume.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,77 @@ +/** + * 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 "../../Messages/IObservable.h" +#include "../Toolbox/Slice.h" + +namespace Deprecated +{ + class ISlicedVolume : public OrthancStone::IObservable + { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, ISlicedVolume); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, ISlicedVolume); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, ISlicedVolume); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeReadyMessage, ISlicedVolume); + + + class SliceContentChangedMessage : public OrthancStone::OriginMessage<ISlicedVolume> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + size_t sliceIndex_; + const Slice& slice_; + + public: + SliceContentChangedMessage(ISlicedVolume& origin, + size_t sliceIndex, + const Slice& slice) : + OriginMessage(origin), + sliceIndex_(sliceIndex), + slice_(slice) + { + } + + size_t GetSliceIndex() const + { + return sliceIndex_; + } + + const Slice& GetSlice() const + { + return slice_; + } + }; + + + ISlicedVolume(OrthancStone::MessageBroker& broker) : + IObservable(broker) + { + } + + virtual size_t GetSliceCount() const = 0; + + virtual const Slice& GetSlice(size_t slice) const = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Volumes/IVolumeLoader.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,40 @@ +/** + * 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 "../../Messages/IObservable.h" + +namespace Deprecated +{ + class IVolumeLoader : public OrthancStone::IObservable + { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeLoader); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeLoader); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeLoader); + + IVolumeLoader(OrthancStone::MessageBroker& broker) : + IObservable(broker) + { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Volumes/StructureSetLoader.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,159 @@ +/** + * 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 "StructureSetLoader.h" + +#include "../Toolbox/MessagingToolbox.h" + +#include <Core/OrthancException.h> + +namespace Deprecated +{ + StructureSetLoader::StructureSetLoader(OrthancStone::MessageBroker& broker, + OrthancApiClient& orthanc) : + IVolumeLoader(broker), + IObserver(broker), + orthanc_(orthanc) + { + } + + + void StructureSetLoader::OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message) + { + OrthancPlugins::FullOrthancDataset dataset(message.GetJson()); + + Orthanc::DicomMap slice; + MessagingToolbox::ConvertDataset(slice, dataset); + structureSet_->AddReferencedSlice(slice); + + BroadcastMessage(ContentChangedMessage(*this)); + } + + + void StructureSetLoader::OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message) + { + OrthancPlugins::FullOrthancDataset dataset(message.GetJson()); + structureSet_.reset(new OrthancStone::DicomStructureSet(dataset)); + + std::set<std::string> instances; + structureSet_->GetReferencedInstances(instances); + + for (std::set<std::string>::const_iterator it = instances.begin(); + it != instances.end(); ++it) + { + orthanc_.PostBinaryAsyncExpectJson("/tools/lookup", *it, + new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnLookupCompleted)); + } + + BroadcastMessage(GeometryReadyMessage(*this)); + } + + + void StructureSetLoader::OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message) + { + const Json::Value& lookup = message.GetJson(); + + if (lookup.type() != Json::arrayValue || + lookup.size() != 1 || + !lookup[0].isMember("Type") || + !lookup[0].isMember("Path") || + lookup[0]["Type"].type() != Json::stringValue || + lookup[0]["ID"].type() != Json::stringValue || + lookup[0]["Type"].asString() != "Instance") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + const std::string& instance = lookup[0]["ID"].asString(); + orthanc_.GetJsonAsync("/instances/" + instance + "/tags", + new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnReferencedSliceLoaded)); + } + + + void StructureSetLoader::ScheduleLoadInstance(const std::string& instance) + { + if (structureSet_.get() != NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + orthanc_.GetJsonAsync("/instances/" + instance + "/tags?ignore-length=3006-0050", + new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnStructureSetLoaded)); + } + } + + + OrthancStone::DicomStructureSet& StructureSetLoader::GetStructureSet() + { + if (structureSet_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *structureSet_; + } + } + + + OrthancStone::DicomStructureSet* StructureSetLoader::SynchronousLoad( + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instanceId) + { + const std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; + OrthancPlugins::FullOrthancDataset dataset(orthanc, uri); + + std::auto_ptr<OrthancStone::DicomStructureSet> result + (new OrthancStone::DicomStructureSet(dataset)); + + std::set<std::string> instances; + result->GetReferencedInstances(instances); + + for (std::set<std::string>::const_iterator it = instances.begin(); + it != instances.end(); ++it) + { + Json::Value lookup; + MessagingToolbox::RestApiPost(lookup, orthanc, "/tools/lookup", *it); + + if (lookup.type() != Json::arrayValue || + lookup.size() != 1 || + !lookup[0].isMember("Type") || + !lookup[0].isMember("Path") || + lookup[0]["Type"].type() != Json::stringValue || + lookup[0]["ID"].type() != Json::stringValue || + lookup[0]["Type"].asString() != "Instance") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + OrthancPlugins::FullOrthancDataset slice + (orthanc, "/instances/" + lookup[0]["ID"].asString() + "/tags"); + Orthanc::DicomMap m; + MessagingToolbox::ConvertDataset(m, slice); + result->AddReferencedSlice(m); + } + + result->CheckReferencedSlices(); + + return result.release(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Volumes/StructureSetLoader.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,61 @@ +/** + * 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/DicomStructureSet.h" +#include "../Toolbox/OrthancApiClient.h" +#include "IVolumeLoader.h" + +namespace Deprecated +{ + class StructureSetLoader : + public IVolumeLoader, + public OrthancStone::IObserver + { + private: + OrthancApiClient& orthanc_; + std::auto_ptr<OrthancStone::DicomStructureSet> structureSet_; + + void OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message); + + void OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message); + + void OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message); + + public: + StructureSetLoader(OrthancStone::MessageBroker& broker, + OrthancApiClient& orthanc); + + void ScheduleLoadInstance(const std::string& instance); + + bool HasStructureSet() const + { + return structureSet_.get() != NULL; + } + + OrthancStone::DicomStructureSet& GetStructureSet(); + + static OrthancStone::DicomStructureSet* SynchronousLoad( + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instanceId); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/CairoWidget.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,101 @@ +/** + * 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 "CairoWidget.h" + +#include <Core/Images/ImageProcessing.h> +#include <Core/OrthancException.h> + +namespace Deprecated +{ + static bool IsAligned(const Orthanc::ImageAccessor& target) + { + // TODO + return true; + } + + CairoWidget::CairoWidget(const std::string& name) : + WidgetBase(name) + { + } + + void CairoWidget::SetSize(unsigned int width, + unsigned int height) + { + surface_.SetSize(width, height, false /* no alpha */); + } + + + bool CairoWidget::Render(Orthanc::ImageAccessor& target) + { + // Don't call the base class here, as + // "ClearBackgroundCairo()" is a faster alternative + + if (IsAligned(target)) + { + OrthancStone::CairoSurface surface(target, false /* no alpha */); + OrthancStone::CairoContext context(surface); + ClearBackgroundCairo(context); + return RenderCairo(context); + } + else + { + OrthancStone::CairoContext context(surface_); + ClearBackgroundCairo(context); + + if (RenderCairo(context)) + { + Orthanc::ImageAccessor surface; + surface_.GetReadOnlyAccessor(surface); + Orthanc::ImageProcessing::Copy(target, surface); + return true; + } + else + { + return false; + } + } + } + + + void CairoWidget::RenderMouseOver(Orthanc::ImageAccessor& target, + int x, + int y) + { + if (IsAligned(target)) + { + OrthancStone::CairoSurface surface(target, false /* no alpha */); + OrthancStone::CairoContext context(surface); + RenderMouseOverCairo(context, x, y); + } + else + { + Orthanc::ImageAccessor accessor; + surface_.GetWriteableAccessor(accessor); + Orthanc::ImageProcessing::Copy(accessor, target); + + OrthancStone::CairoContext context(surface_); + RenderMouseOverCairo(context, x, y); + + Orthanc::ImageProcessing::Copy(target, accessor); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/CairoWidget.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,52 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 "WidgetBase.h" + +namespace Deprecated +{ + class CairoWidget : public WidgetBase + { + private: + OrthancStone::CairoSurface surface_; + + protected: + virtual bool RenderCairo(OrthancStone::CairoContext& context) = 0; + + virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context, + int x, + int y) = 0; + + public: + CairoWidget(const std::string& name); + + virtual void SetSize(unsigned int width, + unsigned int height); + + virtual bool Render(Orthanc::ImageAccessor& target); + + virtual void RenderMouseOver(Orthanc::ImageAccessor& target, + int x, + int y); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/EmptyWidget.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,41 @@ +/** + * 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 "EmptyWidget.h" + +#include <Core/Images/ImageProcessing.h> +#include <Core/OrthancException.h> + +namespace Deprecated +{ + bool EmptyWidget::Render(Orthanc::ImageAccessor& surface) + { + // Note: This call is slow + Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255); + return true; + } + + + void EmptyWidget::DoAnimation() + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/EmptyWidget.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,116 @@ +/** + * 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 "IWidget.h" + +namespace Deprecated +{ + /** + * This is a test widget that simply fills its surface with an + * uniform color. + **/ + class EmptyWidget : public IWidget + { + private: + uint8_t red_; + uint8_t green_; + uint8_t blue_; + + public: + EmptyWidget(uint8_t red, + uint8_t green, + uint8_t blue) : + red_(red), + green_(green), + blue_(blue) + { + } + + virtual void FitContent() + { + } + + virtual void SetParent(IWidget& widget) + { + } + + virtual void SetViewport(WidgetViewport& viewport) + { + } + + virtual void NotifyContentChanged() + { + } + + virtual void SetStatusBar(IStatusBar& statusBar) + { + } + + virtual void SetSize(unsigned int width, + unsigned int height) + { + } + + virtual bool Render(Orthanc::ImageAccessor& surface); + + virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& touches) + { + return NULL; + } + + virtual void RenderMouseOver(Orthanc::ImageAccessor& target, + int x, + int y) + { + } + + virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers) + { + } + + virtual void KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers) + { + } + + virtual bool HasAnimation() const + { + return false; + } + + virtual void DoAnimation(); + + virtual bool HasRenderMouseOver() + { + return false; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/IWidget.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,81 @@ +/** + * 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 "../../StoneEnumerations.h" +#include "../Viewport/IMouseTracker.h" +#include "../Viewport/IStatusBar.h" + +namespace Deprecated +{ + class WidgetViewport; // Forward declaration + + class IWidget : public boost::noncopyable + { + public: + virtual ~IWidget() + { + } + + virtual void FitContent() = 0; + + virtual void SetParent(IWidget& parent) = 0; + + virtual void SetViewport(WidgetViewport& viewport) = 0; + + virtual void SetStatusBar(IStatusBar& statusBar) = 0; + + virtual void SetSize(unsigned int width, + unsigned int height) = 0; + + virtual bool Render(Orthanc::ImageAccessor& surface) = 0; + + virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& touches) = 0; + + virtual void RenderMouseOver(Orthanc::ImageAccessor& target, + int x, + int y) = 0; + + virtual bool HasRenderMouseOver() = 0; + + virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers) = 0; + + virtual void KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers) = 0; + + virtual bool HasAnimation() const = 0; + + virtual void DoAnimation() = 0; + + // Subclasses can call this method to signal the display of the + // widget must be refreshed + virtual void NotifyContentChanged() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/IWorldSceneInteractor.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,70 @@ +/** + * 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 "IWorldSceneMouseTracker.h" + +#include "../Toolbox/ViewportGeometry.h" +#include "../../StoneEnumerations.h" +#include "../Viewport/IStatusBar.h" + +namespace Deprecated +{ + class WorldSceneWidget; + + class IWorldSceneInteractor : public boost::noncopyable + { + public: + virtual ~IWorldSceneInteractor() + { + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + OrthancStone::MouseButton button, + OrthancStone::KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + IStatusBar* statusBar, + const std::vector<Touch>& touches) = 0; + + virtual void MouseOver(OrthancStone::CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) = 0; + + virtual void MouseWheel(WorldSceneWidget& widget, + OrthancStone::MouseWheelDirection direction, + OrthancStone::KeyboardModifiers modifiers, + IStatusBar* statusBar) = 0; + + virtual void KeyPressed(WorldSceneWidget& widget, + OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers, + IStatusBar* statusBar) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/IWorldSceneMouseTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,54 @@ +/** + * 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 "../../Wrappers/CairoContext.h" +#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition + +namespace Deprecated +{ + + // this is tracking a mouse in scene coordinates/mm unlike + // the IMouseTracker that is tracking a mouse + // in screen coordinates/pixels. + class IWorldSceneMouseTracker : public boost::noncopyable + { + public: + virtual ~IWorldSceneMouseTracker() + { + } + + virtual bool HasRender() const = 0; + + virtual void Render(OrthancStone::CairoContext& context, + double zoom) = 0; + + virtual void MouseUp() = 0; + + virtual void MouseMove(int displayX, + int displayY, + double sceneX, + double sceneY, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/LayoutWidget.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,503 @@ +/** + * 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 "LayoutWidget.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +#include <boost/math/special_functions/round.hpp> + +namespace Deprecated +{ + class LayoutWidget::LayoutMouseTracker : public IMouseTracker + { + private: + std::auto_ptr<IMouseTracker> tracker_; + int left_; + int top_; + unsigned int width_; + unsigned int height_; + + public: + LayoutMouseTracker(IMouseTracker* tracker, + int left, + int top, + unsigned int width, + unsigned int height) : + tracker_(tracker), + left_(left), + top_(top), + width_(width), + height_(height) + { + if (tracker == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + virtual void Render(Orthanc::ImageAccessor& surface) + { + Orthanc::ImageAccessor accessor; + surface.GetRegion(accessor, left_, top_, width_, height_); + tracker_->Render(accessor); + } + + virtual void MouseUp() + { + tracker_->MouseUp(); + } + + virtual void MouseMove(int x, + int y, + const std::vector<Touch>& displayTouches) + { + 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); + } + }; + + + class LayoutWidget::ChildWidget : public boost::noncopyable + { + private: + std::auto_ptr<IWidget> widget_; + int left_; + int top_; + unsigned int width_; + unsigned int height_; + + public: + ChildWidget(IWidget* widget) : + widget_(widget) + { + assert(widget != NULL); + SetEmpty(); + } + + void DoAnimation() + { + if (widget_->HasAnimation()) + { + widget_->DoAnimation(); + } + } + + IWidget& GetWidget() const + { + return *widget_; + } + + void SetRectangle(unsigned int left, + unsigned int top, + unsigned int width, + unsigned int height) + { + left_ = left; + top_ = top; + width_ = width; + height_ = height; + + widget_->SetSize(width, height); + } + + void SetEmpty() + { + SetRectangle(0, 0, 0, 0); + } + + bool Contains(int x, + int y) const + { + return (x >= left_ && + y >= top_ && + x < left_ + static_cast<int>(width_) && + y < top_ + static_cast<int>(height_)); + } + + bool Render(Orthanc::ImageAccessor& target) + { + if (width_ == 0 || + height_ == 0) + { + return true; + } + else + { + Orthanc::ImageAccessor accessor; + target.GetRegion(accessor, left_, top_, width_, height_); + return widget_->Render(accessor); + } + } + + IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& touches) + { + if (Contains(x, y)) + { + IMouseTracker* tracker = widget_->CreateMouseTracker(button, + x - left_, + y - top_, + modifiers, + touches); + if (tracker) + { + return new LayoutMouseTracker(tracker, left_, top_, width_, height_); + } + } + + return NULL; + } + + void RenderMouseOver(Orthanc::ImageAccessor& target, + int x, + int y) + { + if (Contains(x, y)) + { + Orthanc::ImageAccessor accessor; + target.GetRegion(accessor, left_, top_, width_, height_); + + widget_->RenderMouseOver(accessor, x - left_, y - top_); + } + } + + void MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers) + { + if (Contains(x, y)) + { + widget_->MouseWheel(direction, x - left_, y - top_, modifiers); + } + } + + bool HasRenderMouseOver() + { + return widget_->HasRenderMouseOver(); + } + }; + + + void LayoutWidget::ComputeChildrenExtents() + { + if (children_.size() == 0) + { + return; + } + + float internal = static_cast<float>(paddingInternal_); + + if (width_ <= paddingLeft_ + paddingRight_ || + height_ <= paddingTop_ + paddingBottom_) + { + for (size_t i = 0; i < children_.size(); i++) + { + children_[i]->SetEmpty(); + } + } + else if (isHorizontal_) + { + 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())); + + for (size_t i = 0; i < children_.size(); i++) + { + float left = static_cast<float>(paddingLeft_) + static_cast<float>(i) * (childWidth + internal); + float right = left + childWidth; + + if (left >= right) + { + children_[i]->SetEmpty(); + } + else + { + children_[i]->SetRectangle(static_cast<unsigned int>(left), + paddingTop_, + boost::math::iround(right - left), + height_ - paddingTop_ - paddingBottom_); + } + } + } + else + { + 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())); + + for (size_t i = 0; i < children_.size(); i++) + { + float top = static_cast<float>(paddingTop_) + static_cast<float>(i) * (childHeight + internal); + float bottom = top + childHeight; + + if (top >= bottom) + { + children_[i]->SetEmpty(); + } + else + { + children_[i]->SetRectangle(paddingTop_, + static_cast<unsigned int>(top), + width_ - paddingLeft_ - paddingRight_, + boost::math::iround(bottom - top)); + } + } + } + + NotifyContentChanged(*this); + } + + + LayoutWidget::LayoutWidget(const std::string& name) : + WidgetBase(name), + isHorizontal_(true), + width_(0), + height_(0), + paddingLeft_(0), + paddingTop_(0), + paddingRight_(0), + paddingBottom_(0), + paddingInternal_(0) + { + } + + + LayoutWidget::~LayoutWidget() + { + for (size_t i = 0; i < children_.size(); i++) + { + delete children_[i]; + } + } + + + void LayoutWidget::FitContent() + { + for (size_t i = 0; i < children_.size(); i++) + { + children_[i]->GetWidget().FitContent(); + } + } + + + void LayoutWidget::NotifyContentChanged(const IWidget& widget) + { + // One of the children has changed + WidgetBase::NotifyContentChanged(); + } + + + void LayoutWidget::SetHorizontal() + { + isHorizontal_ = true; + ComputeChildrenExtents(); + } + + + void LayoutWidget::SetVertical() + { + isHorizontal_ = false; + ComputeChildrenExtents(); + } + + + void LayoutWidget::SetPadding(unsigned int left, + unsigned int top, + unsigned int right, + unsigned int bottom, + unsigned int spacing) + { + paddingLeft_ = left; + paddingTop_ = top; + paddingRight_ = right; + paddingBottom_ = bottom; + paddingInternal_ = spacing; + } + + + void LayoutWidget::SetPadding(unsigned int padding) + { + paddingLeft_ = padding; + paddingTop_ = padding; + paddingRight_ = padding; + paddingBottom_ = padding; + paddingInternal_ = padding; + } + + + IWidget& LayoutWidget::AddWidget(IWidget* widget) // Takes ownership + { + if (widget == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (GetStatusBar() != NULL) + { + widget->SetStatusBar(*GetStatusBar()); + } + + children_.push_back(new ChildWidget(widget)); + widget->SetParent(*this); + + ComputeChildrenExtents(); + + if (widget->HasAnimation()) + { + hasAnimation_ = true; + } + + return *widget; + } + + + void LayoutWidget::SetStatusBar(IStatusBar& statusBar) + { + WidgetBase::SetStatusBar(statusBar); + + for (size_t i = 0; i < children_.size(); i++) + { + children_[i]->GetWidget().SetStatusBar(statusBar); + } + } + + + void LayoutWidget::SetSize(unsigned int width, + unsigned int height) + { + width_ = width; + height_ = height; + ComputeChildrenExtents(); + } + + + bool LayoutWidget::Render(Orthanc::ImageAccessor& surface) + { + if (!WidgetBase::Render(surface)) + { + return false; + } + + for (size_t i = 0; i < children_.size(); i++) + { + if (!children_[i]->Render(surface)) + { + return false; + } + } + + return true; + } + + + IMouseTracker* LayoutWidget::CreateMouseTracker(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::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, touches); + if (tracker != NULL) + { + return tracker; + } + } + + return NULL; + } + + + void LayoutWidget::RenderMouseOver(Orthanc::ImageAccessor& target, + int x, + int y) + { + for (size_t i = 0; i < children_.size(); i++) + { + children_[i]->RenderMouseOver(target, x, y); + } + } + + + void LayoutWidget::MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers) + { + for (size_t i = 0; i < children_.size(); i++) + { + children_[i]->MouseWheel(direction, x, y, modifiers); + } + } + + + void LayoutWidget::KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers) + { + for (size_t i = 0; i < children_.size(); i++) + { + children_[i]->GetWidget().KeyPressed(key, keyChar, modifiers); + } + } + + + void LayoutWidget::DoAnimation() + { + if (hasAnimation_) + { + for (size_t i = 0; i < children_.size(); i++) + { + children_[i]->DoAnimation(); + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + bool LayoutWidget::HasRenderMouseOver() + { + for (size_t i = 0; i < children_.size(); i++) + { + if (children_[i]->HasRenderMouseOver()) + { + return true; + } + } + + return false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/LayoutWidget.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,134 @@ +/** + * 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 "WidgetBase.h" + +#include <vector> +#include <memory> + +namespace Deprecated +{ + class LayoutWidget : public WidgetBase + { + private: + class LayoutMouseTracker; + class ChildWidget; + + std::vector<ChildWidget*> children_; + bool isHorizontal_; + unsigned int width_; + unsigned int height_; + std::auto_ptr<IMouseTracker> mouseTracker_; + unsigned int paddingLeft_; + unsigned int paddingTop_; + unsigned int paddingRight_; + unsigned int paddingBottom_; + unsigned int paddingInternal_; + bool hasAnimation_; + + void ComputeChildrenExtents(); + + public: + LayoutWidget(const std::string& name); + + virtual ~LayoutWidget(); + + virtual void FitContent(); + + virtual void NotifyContentChanged(const IWidget& widget); + + void SetHorizontal(); + + void SetVertical(); + + void SetPadding(unsigned int left, + unsigned int top, + unsigned int right, + unsigned int bottom, + unsigned int spacing); + + void SetPadding(unsigned int padding); + + unsigned int GetPaddingLeft() const + { + return paddingLeft_; + } + + unsigned int GetPaddingTop() const + { + return paddingTop_; + } + + unsigned int GetPaddingRight() const + { + return paddingRight_; + } + + unsigned int GetPaddingBottom() const + { + return paddingBottom_; + } + + unsigned int GetPaddingInternal() const + { + return paddingInternal_; + } + + IWidget& AddWidget(IWidget* widget); // Takes ownership + + virtual void SetStatusBar(IStatusBar& statusBar); + + virtual void SetSize(unsigned int width, + unsigned int height); + + virtual bool Render(Orthanc::ImageAccessor& surface); + + virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& touches); + + virtual void RenderMouseOver(Orthanc::ImageAccessor& target, + int x, + int y); + + virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers); + + virtual void KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers); + + virtual bool HasAnimation() const + { + return hasAnimation_; + } + + virtual void DoAnimation(); + + virtual bool HasRenderMouseOver(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/PanMouseTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,58 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 "PanMouseTracker.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +namespace Deprecated +{ + PanMouseTracker::PanMouseTracker(WorldSceneWidget& that, + int x, + int y) : + that_(that) + { + that.GetView().GetPan(originalPanX_, originalPanY_); + that.GetView().MapPixelCenterToScene(downX_, downY_, x, y); + } + + + void PanMouseTracker::Render(OrthancStone::CairoContext& context, + double zoom) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + + void PanMouseTracker::MouseMove(int displayX, + int displayY, + double x, + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) + { + ViewportGeometry view = that_.GetView(); + view.SetPan(originalPanX_ + (x - downX_) * view.GetZoom(), + originalPanY_ + (y - downY_) * view.GetZoom()); + that_.SetView(view); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/PanMouseTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,61 @@ +/** + * 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 Deprecated +{ + class PanMouseTracker : public IWorldSceneMouseTracker + { + private: + WorldSceneWidget& that_; + double originalPanX_; + double originalPanY_; + double downX_; + double downY_; + + public: + PanMouseTracker(WorldSceneWidget& that, + int x, + int y); + + virtual bool HasRender() const + { + return false; + } + + virtual void MouseUp() + { + } + + virtual void Render(OrthancStone::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); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/PanZoomMouseTracker.cpp Mon Jun 24 14:35: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 Deprecated +{ + 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(OrthancStone::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/Deprecated/Widgets/PanZoomMouseTracker.h Mon Jun 24 14:35: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 Deprecated +{ + 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(OrthancStone::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); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/SliceViewerWidget.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,654 @@ +/** + * 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 "SliceViewerWidget.h" + +#include "../Layers/SliceOutlineRenderer.h" +#include "../../Toolbox/GeometryToolbox.h" +#include "../Layers/FrameRenderer.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +#include <boost/math/constants/constants.hpp> + + +static const double THIN_SLICE_THICKNESS = 100.0 * std::numeric_limits<double>::epsilon(); + +namespace Deprecated +{ + class SliceViewerWidget::Scene : public boost::noncopyable + { + private: + OrthancStone::CoordinateSystem3D plane_; + double thickness_; + size_t countMissing_; + std::vector<ILayerRenderer*> renderers_; + + public: + void DeleteLayer(size_t index) + { + if (index >= renderers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(countMissing_ <= renderers_.size()); + + if (renderers_[index] != NULL) + { + assert(countMissing_ < renderers_.size()); + delete renderers_[index]; + renderers_[index] = NULL; + countMissing_++; + } + } + + Scene(const OrthancStone::CoordinateSystem3D& plane, + double thickness, + size_t countLayers) : + plane_(plane), + thickness_(thickness), + countMissing_(countLayers), + renderers_(countLayers, NULL) + { + if (thickness <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + ~Scene() + { + for (size_t i = 0; i < renderers_.size(); i++) + { + DeleteLayer(i); + } + } + + void SetLayer(size_t index, + ILayerRenderer* renderer) // Takes ownership + { + if (renderer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + DeleteLayer(index); + + renderers_[index] = renderer; + countMissing_--; + } + + const OrthancStone::CoordinateSystem3D& GetPlane() const + { + return plane_; + } + + bool HasRenderer(size_t index) + { + return renderers_[index] != NULL; + } + + bool IsComplete() const + { + return countMissing_ == 0; + } + + unsigned int GetCountMissing() const + { + return static_cast<unsigned int>(countMissing_); + } + + bool RenderScene(OrthancStone::CairoContext& context, + const ViewportGeometry& view, + const OrthancStone::CoordinateSystem3D& viewportPlane) + { + bool fullQuality = true; + cairo_t *cr = context.GetObject(); + + for (size_t i = 0; i < renderers_.size(); i++) + { + if (renderers_[i] != NULL) + { + const OrthancStone::CoordinateSystem3D& framePlane = renderers_[i]->GetLayerPlane(); + + double x0, y0, x1, y1, x2, y2; + viewportPlane.ProjectPoint(x0, y0, framePlane.GetOrigin()); + viewportPlane.ProjectPoint(x1, y1, framePlane.GetOrigin() + framePlane.GetAxisX()); + viewportPlane.ProjectPoint(x2, y2, framePlane.GetOrigin() + framePlane.GetAxisY()); + + /** + * Now we solve the system of linear equations Ax + b = x', given: + * A [0 ; 0] + b = [x0 ; y0] + * A [1 ; 0] + b = [x1 ; y1] + * A [0 ; 1] + b = [x2 ; y2] + * <=> + * b = [x0 ; y0] + * A [1 ; 0] = [x1 ; y1] - b = [x1 - x0 ; y1 - y0] + * A [0 ; 1] = [x2 ; y2] - b = [x2 - x0 ; y2 - y0] + * <=> + * b = [x0 ; y0] + * [a11 ; a21] = [x1 - x0 ; y1 - y0] + * [a12 ; a22] = [x2 - x0 ; y2 - y0] + **/ + + cairo_matrix_t transform; + cairo_matrix_init(&transform, x1 - x0, y1 - y0, x2 - x0, y2 - y0, x0, y0); + + cairo_save(cr); + cairo_transform(cr, &transform); + + if (!renderers_[i]->RenderLayer(context, view)) + { + cairo_restore(cr); + return false; + } + + cairo_restore(cr); + } + + if (renderers_[i] != NULL && + !renderers_[i]->IsFullQuality()) + { + fullQuality = false; + } + } + + if (!fullQuality) + { + double x, y; + view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10); + + cairo_translate(cr, x, y); + +#if 1 + double s = 5.0 / view.GetZoom(); + cairo_rectangle(cr, -s, -s, 2.0 * s, 2.0 * s); +#else + // TODO Drawing filled circles makes WebAssembly crash! + cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2.0 * boost::math::constants::pi<double>()); +#endif + + cairo_set_line_width(cr, 2.0 / view.GetZoom()); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_stroke_preserve(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_fill(cr); + } + + return true; + } + + void SetLayerStyle(size_t index, + const RenderStyle& style) + { + if (renderers_[index] != NULL) + { + renderers_[index]->SetLayerStyle(style); + } + } + + bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const + { + bool isOpposite; + if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, + plane.GetNormal(), + plane_.GetNormal())) + { + return false; + } + else + { + double z = (plane_.ProjectAlongNormal(plane.GetOrigin()) - + plane_.ProjectAlongNormal(plane_.GetOrigin())); + + if (z < 0) + { + z = -z; + } + + return z <= thickness_; + } + } + + double GetThickness() const + { + return thickness_; + } + }; + + + bool SliceViewerWidget::LookupLayer(size_t& index /* out */, + const IVolumeSlicer& layer) const + { + LayersIndex::const_iterator found = layersIndex_.find(&layer); + + if (found == layersIndex_.end()) + { + return false; + } + else + { + index = found->second; + assert(index < layers_.size() && + layers_[index] == &layer); + return true; + } + } + + + void SliceViewerWidget::GetLayerExtent(OrthancStone::Extent2D& extent, + IVolumeSlicer& source) const + { + extent.Reset(); + + std::vector<OrthancStone::Vector> points; + if (source.GetExtent(points, plane_)) + { + for (size_t i = 0; i < points.size(); i++) + { + double x, y; + plane_.ProjectPoint(x, y, points[i]); + extent.AddPoint(x, y); + } + } + } + + + OrthancStone::Extent2D SliceViewerWidget::GetSceneExtent() + { + OrthancStone::Extent2D sceneExtent; + + for (size_t i = 0; i < layers_.size(); i++) + { + assert(layers_[i] != NULL); + OrthancStone::Extent2D layerExtent; + GetLayerExtent(layerExtent, *layers_[i]); + + sceneExtent.Union(layerExtent); + } + + return sceneExtent; + } + + + bool SliceViewerWidget::RenderScene(OrthancStone::CairoContext& context, + const ViewportGeometry& view) + { + if (currentScene_.get() != NULL) + { + return currentScene_->RenderScene(context, view, plane_); + } + else + { + return true; + } + } + + + void SliceViewerWidget::ResetPendingScene() + { + double thickness; + if (pendingScene_.get() == NULL) + { + thickness = 1.0; + } + else + { + thickness = pendingScene_->GetThickness(); + } + + pendingScene_.reset(new Scene(plane_, thickness, layers_.size())); + } + + + void SliceViewerWidget::UpdateLayer(size_t index, + ILayerRenderer* renderer, + const OrthancStone::CoordinateSystem3D& plane) + { + LOG(INFO) << "Updating layer " << index; + + std::auto_ptr<ILayerRenderer> tmp(renderer); + + if (renderer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (index >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(layers_.size() == styles_.size()); + renderer->SetLayerStyle(styles_[index]); + + if (currentScene_.get() != NULL && + currentScene_->ContainsPlane(plane)) + { + currentScene_->SetLayer(index, tmp.release()); + NotifyContentChanged(); + } + else if (pendingScene_.get() != NULL && + pendingScene_->ContainsPlane(plane)) + { + pendingScene_->SetLayer(index, tmp.release()); + + if (currentScene_.get() == NULL || + !currentScene_->IsComplete() || + pendingScene_->IsComplete()) + { + currentScene_ = pendingScene_; + NotifyContentChanged(); + } + } + } + + + SliceViewerWidget::SliceViewerWidget(OrthancStone::MessageBroker& broker, + const std::string& name) : + WorldSceneWidget(name), + IObserver(broker), + IObservable(broker), + started_(false) + { + SetBackgroundCleared(true); + } + + + SliceViewerWidget::~SliceViewerWidget() + { + for (size_t i = 0; i < layers_.size(); i++) + { + delete layers_[i]; + } + } + + void SliceViewerWidget::ObserveLayer(IVolumeSlicer& layer) + { + layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::GeometryReadyMessage> + (*this, &SliceViewerWidget::OnGeometryReady)); + // currently ignore errors layer->RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::GeometryErrorMessage>(*this, &SliceViewerWidget::...)); + layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::SliceContentChangedMessage> + (*this, &SliceViewerWidget::OnSliceChanged)); + layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::ContentChangedMessage> + (*this, &SliceViewerWidget::OnContentChanged)); + layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerReadyMessage> + (*this, &SliceViewerWidget::OnLayerReady)); + layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerErrorMessage> + (*this, &SliceViewerWidget::OnLayerError)); + } + + + size_t SliceViewerWidget::AddLayer(IVolumeSlicer* layer) // Takes ownership + { + if (layer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + size_t index = layers_.size(); + layers_.push_back(layer); + styles_.push_back(RenderStyle()); + layersIndex_[layer] = index; + + ResetPendingScene(); + + ObserveLayer(*layer); + + ResetChangedLayers(); + + return index; + } + + + void SliceViewerWidget::ReplaceLayer(size_t index, IVolumeSlicer* layer) // Takes ownership + { + if (layer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (index >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + delete layers_[index]; + layers_[index] = layer; + layersIndex_[layer] = index; + + ResetPendingScene(); + + ObserveLayer(*layer); + + InvalidateLayer(index); + } + + + void SliceViewerWidget::RemoveLayer(size_t index) + { + if (index >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + IVolumeSlicer* previousLayer = layers_[index]; + layersIndex_.erase(layersIndex_.find(previousLayer)); + layers_.erase(layers_.begin() + index); + changedLayers_.erase(changedLayers_.begin() + index); + styles_.erase(styles_.begin() + index); + + delete layers_[index]; + + currentScene_->DeleteLayer(index); + ResetPendingScene(); + + NotifyContentChanged(); + } + + + const RenderStyle& SliceViewerWidget::GetLayerStyle(size_t layer) const + { + if (layer >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(layers_.size() == styles_.size()); + return styles_[layer]; + } + + + void SliceViewerWidget::SetLayerStyle(size_t layer, + const RenderStyle& style) + { + if (layer >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(layers_.size() == styles_.size()); + styles_[layer] = style; + + if (currentScene_.get() != NULL) + { + currentScene_->SetLayerStyle(layer, style); + } + + if (pendingScene_.get() != NULL) + { + pendingScene_->SetLayerStyle(layer, style); + } + + NotifyContentChanged(); + } + + + void SliceViewerWidget::SetSlice(const OrthancStone::CoordinateSystem3D& plane) + { + LOG(INFO) << "Setting slice origin: (" << plane.GetOrigin()[0] + << "," << plane.GetOrigin()[1] + << "," << plane.GetOrigin()[2] << ")"; + + Deprecated::Slice displayedSlice(plane_, THIN_SLICE_THICKNESS); + + //if (!displayedSlice.ContainsPlane(slice)) + { + if (currentScene_.get() == NULL || + (pendingScene_.get() != NULL && + pendingScene_->IsComplete())) + { + currentScene_ = pendingScene_; + } + + plane_ = plane; + ResetPendingScene(); + + InvalidateAllLayers(); // TODO Removing this line avoid loading twice the image in WASM + } + + BroadcastMessage(DisplayedSliceMessage(*this, displayedSlice)); + } + + + void SliceViewerWidget::OnGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message) + { + size_t i; + if (LookupLayer(i, message.GetOrigin())) + { + LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName(); + + changedLayers_[i] = true; + //layers_[i]->ScheduleLayerCreation(plane_); + } + BroadcastMessage(GeometryChangedMessage(*this)); + } + + + void SliceViewerWidget::InvalidateAllLayers() + { + for (size_t i = 0; i < layers_.size(); i++) + { + assert(layers_[i] != NULL); + changedLayers_[i] = true; + + //layers_[i]->ScheduleLayerCreation(plane_); + } + } + + + void SliceViewerWidget::InvalidateLayer(size_t layer) + { + if (layer >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(layers_[layer] != NULL); + changedLayers_[layer] = true; + + //layers_[layer]->ScheduleLayerCreation(plane_); + } + + + void SliceViewerWidget::OnContentChanged(const IVolumeSlicer::ContentChangedMessage& message) + { + size_t index; + if (LookupLayer(index, message.GetOrigin())) + { + InvalidateLayer(index); + } + + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); + } + + + void SliceViewerWidget::OnSliceChanged(const IVolumeSlicer::SliceContentChangedMessage& message) + { + if (message.GetSlice().ContainsPlane(plane_)) + { + size_t index; + if (LookupLayer(index, message.GetOrigin())) + { + InvalidateLayer(index); + } + } + + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); + } + + + void SliceViewerWidget::OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message) + { + size_t index; + if (LookupLayer(index, message.GetOrigin())) + { + LOG(INFO) << "Renderer ready for layer " << index; + UpdateLayer(index, message.CreateRenderer(), message.GetSlice()); + } + + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); + } + + + void SliceViewerWidget::OnLayerError(const IVolumeSlicer::LayerErrorMessage& message) + { + size_t index; + if (LookupLayer(index, message.GetOrigin())) + { + LOG(ERROR) << "Using error renderer on layer " << index; + + // TODO + //UpdateLayer(index, new SliceOutlineRenderer(slice), slice); + + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); + } + } + + + void SliceViewerWidget::ResetChangedLayers() + { + changedLayers_.resize(layers_.size()); + + for (size_t i = 0; i < changedLayers_.size(); i++) + { + changedLayers_[i] = false; + } + } + + + void SliceViewerWidget::DoAnimation() + { + assert(changedLayers_.size() <= layers_.size()); + + for (size_t i = 0; i < changedLayers_.size(); i++) + { + if (changedLayers_[i]) + { + layers_[i]->ScheduleLayerCreation(plane_); + } + } + + ResetChangedLayers(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/SliceViewerWidget.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,155 @@ +/** + * 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" +#include "../Layers/IVolumeSlicer.h" +#include "../../Toolbox/Extent2D.h" +#include "../../Messages/IObserver.h" + +#include <map> + +namespace Deprecated +{ + class SliceViewerWidget : + public WorldSceneWidget, + public OrthancStone::IObserver, + public OrthancStone::IObservable + { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryChangedMessage, SliceViewerWidget); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, SliceViewerWidget); + + + // TODO - Use this message in ReferenceLineSource + class DisplayedSliceMessage : public OrthancStone::OriginMessage<SliceViewerWidget> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const Deprecated::Slice& slice_; + + public: + DisplayedSliceMessage(SliceViewerWidget& origin, + const Deprecated::Slice& slice) : + OriginMessage(origin), + slice_(slice) + { + } + + const Deprecated::Slice& GetSlice() const + { + return slice_; + } + }; + + private: + SliceViewerWidget(const SliceViewerWidget&); + SliceViewerWidget& operator=(const SliceViewerWidget&); + + class Scene; + + typedef std::map<const IVolumeSlicer*, size_t> LayersIndex; + + bool started_; + LayersIndex layersIndex_; + std::vector<IVolumeSlicer*> layers_; + std::vector<RenderStyle> styles_; + OrthancStone::CoordinateSystem3D plane_; + std::auto_ptr<Scene> currentScene_; + std::auto_ptr<Scene> pendingScene_; + std::vector<bool> changedLayers_; + + bool LookupLayer(size_t& index /* out */, + const IVolumeSlicer& layer) const; + + void GetLayerExtent(OrthancStone::Extent2D& extent, + IVolumeSlicer& source) const; + + void OnGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message); + + virtual void OnContentChanged(const IVolumeSlicer::ContentChangedMessage& message); + + virtual void OnSliceChanged(const IVolumeSlicer::SliceContentChangedMessage& message); + + virtual void OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message); + + virtual void OnLayerError(const IVolumeSlicer::LayerErrorMessage& message); + + void ObserveLayer(IVolumeSlicer& source); + + void ResetChangedLayers(); + + public: + SliceViewerWidget(OrthancStone::MessageBroker& broker, + const std::string& name); + + virtual OrthancStone::Extent2D GetSceneExtent(); + + protected: + virtual bool RenderScene(OrthancStone::CairoContext& context, + const ViewportGeometry& view); + + void ResetPendingScene(); + + void UpdateLayer(size_t index, + ILayerRenderer* renderer, + const OrthancStone::CoordinateSystem3D& plane); + + void InvalidateAllLayers(); + + void InvalidateLayer(size_t layer); + + public: + virtual ~SliceViewerWidget(); + + size_t AddLayer(IVolumeSlicer* layer); // Takes ownership + + void ReplaceLayer(size_t layerIndex, IVolumeSlicer* layer); // Takes ownership + + void RemoveLayer(size_t layerIndex); + + size_t GetLayerCount() const + { + return layers_.size(); + } + + const RenderStyle& GetLayerStyle(size_t layer) const; + + void SetLayerStyle(size_t layer, + const RenderStyle& style); + + void SetSlice(const OrthancStone::CoordinateSystem3D& plane); + + const OrthancStone::CoordinateSystem3D& GetSlice() const + { + return plane_; + } + + virtual bool HasAnimation() const + { + return true; + } + + virtual void DoAnimation(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/TestCairoWidget.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,126 @@ +/** + * 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 "TestCairoWidget.h" + +#include <stdio.h> + + +namespace Deprecated +{ + namespace Samples + { + void TestCairoWidget::DoAnimation() + { + value_ -= 0.01f; + if (value_ < 0) + { + value_ = 1; + } + + NotifyContentChanged(); + } + + + bool TestCairoWidget::RenderCairo(OrthancStone::CairoContext& context) + { + cairo_t* cr = context.GetObject(); + + cairo_set_source_rgb (cr, .3, 0, 0); + cairo_paint(cr); + + cairo_set_source_rgb(cr, 0, 1, 0); + cairo_rectangle(cr, width_ / 4, height_ / 4, width_ / 2, height_ / 2); + cairo_set_line_width(cr, 1.0); + cairo_fill(cr); + + cairo_set_source_rgb(cr, 0, 1, value_); + cairo_rectangle(cr, width_ / 2 - 50, height_ / 2 - 50, 100, 100); + cairo_fill(cr); + + return true; + } + + + void TestCairoWidget::RenderMouseOverCairo(OrthancStone::CairoContext& context, + int x, + int y) + { + cairo_t* cr = context.GetObject(); + + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_rectangle(cr, x - 5, y - 5, 10, 10); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); + + char buf[64]; + sprintf(buf, "(%d,%d)", x, y); + UpdateStatusBar(buf); + } + + + TestCairoWidget::TestCairoWidget(const std::string& name, bool animate) : + CairoWidget(name), + width_(0), + height_(0), + value_(1), + animate_(animate) + { + } + + + void TestCairoWidget::SetSize(unsigned int width, + unsigned int height) + { + CairoWidget::SetSize(width, height); + width_ = width; + height_ = height; + } + + + IMouseTracker* TestCairoWidget::CreateMouseTracker(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& touches) + { + UpdateStatusBar("Click"); + return NULL; + } + + + void TestCairoWidget::MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers) + { + UpdateStatusBar(direction == OrthancStone::MouseWheelDirection_Down ? "Wheel down" : "Wheel up"); + } + + + void TestCairoWidget::KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers) + { + UpdateStatusBar("Key pressed: \"" + std::string(1, keyChar) + "\""); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/TestCairoWidget.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,79 @@ +/** + * 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 "CairoWidget.h" + +namespace Deprecated +{ + namespace Samples + { + class TestCairoWidget : public CairoWidget + { + private: + unsigned int width_; + unsigned int height_; + float value_; + bool animate_; + + protected: + virtual bool RenderCairo(OrthancStone::CairoContext& context); + + virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context, + int x, + int y); + + public: + TestCairoWidget(const std::string& name, bool animate); + + virtual void SetSize(unsigned int width, + unsigned int height); + + virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& touches); + + virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers); + + virtual void KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers); + + virtual bool HasAnimation() const + { + return animate_; + } + + virtual void DoAnimation(); + + virtual bool HasRenderMouseOver() + { + return true; + } + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/TestWorldSceneWidget.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,149 @@ +/** + * 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 "TestWorldSceneWidget.h" + +#include <Core/OrthancException.h> + +#include <math.h> +#include <stdio.h> + +namespace Deprecated +{ + namespace Samples + { + class TestWorldSceneWidget::Interactor : public IWorldSceneInteractor + { + public: + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + OrthancStone::MouseButton button, + OrthancStone::KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + IStatusBar* statusBar, + const std::vector<Touch>& touches) + { + if (statusBar) + { + char buf[64]; + sprintf(buf, "X = %0.2f, Y = %0.2f", x, y); + statusBar->SetMessage(buf); + } + + return NULL; + } + + virtual void MouseOver(OrthancStone::CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) + { + double S = 0.5; + + if (fabs(x) <= S && + fabs(y) <= S) + { + cairo_t* cr = context.GetObject(); + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_rectangle(cr, -S, -S , 2.0 * S, 2.0 * S); + cairo_set_line_width(cr, 1.0 / view.GetZoom()); + cairo_stroke(cr); + } + } + + virtual void MouseWheel(WorldSceneWidget& widget, + OrthancStone::MouseWheelDirection direction, + OrthancStone::KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + if (statusBar) + { + statusBar->SetMessage(direction == OrthancStone::MouseWheelDirection_Down ? "Wheel down" : "Wheel up"); + } + } + + virtual void KeyPressed(WorldSceneWidget& widget, + OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + if (statusBar) + { + statusBar->SetMessage("Key pressed: \"" + std::string(1, keyChar) + "\""); + } + } + }; + + + bool TestWorldSceneWidget::RenderScene(OrthancStone::CairoContext& context, + const ViewportGeometry& view) + { + cairo_t* cr = context.GetObject(); + + // Clear background + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_paint(cr); + + float color = static_cast<float>(count_ % 16) / 15.0f; + cairo_set_source_rgb(cr, 0, 1.0f - color, color); + cairo_rectangle(cr, -10, -.5, 20, 1); + cairo_fill(cr); + + return true; + } + + + TestWorldSceneWidget::TestWorldSceneWidget(const std::string& name, bool animate) : + WorldSceneWidget(name), + interactor_(new Interactor), + animate_(animate), + count_(0) + { + SetInteractor(*interactor_); + } + + + OrthancStone::Extent2D TestWorldSceneWidget::GetSceneExtent() + { + return OrthancStone::Extent2D(-10, -.5, 10, .5); + } + + + void TestWorldSceneWidget::DoAnimation() + { + if (animate_) + { + count_++; + NotifyContentChanged(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/TestWorldSceneWidget.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,63 @@ +/** + * 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" + +#include <memory> + +namespace Deprecated +{ + namespace Samples + { + class TestWorldSceneWidget : public WorldSceneWidget + { + private: + class Interactor; + + std::auto_ptr<Interactor> interactor_; + bool animate_; + unsigned int count_; + + protected: + virtual bool RenderScene(OrthancStone::CairoContext& context, + const ViewportGeometry& view); + + public: + TestWorldSceneWidget(const std::string& name, bool animate); + + virtual OrthancStone::Extent2D GetSceneExtent(); + + virtual bool HasAnimation() const + { + return animate_; + } + + virtual void DoAnimation(); + + virtual bool HasRenderMouseOver() + { + return true; + } + }; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/WidgetBase.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,166 @@ +/** + * 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 "WidgetBase.h" + +#include <Core/OrthancException.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Logging.h> + +namespace Deprecated +{ + void WidgetBase::NotifyContentChanged() + { + if (parent_ != NULL) + { + parent_->NotifyContentChanged(); + } + + if (viewport_ != NULL) + { + viewport_->NotifyBackgroundChanged(); + } + } + + + void WidgetBase::SetParent(IWidget& parent) + { + if (parent_ != NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + parent_ = &parent; + } + } + + + void WidgetBase::ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const + { + // Clear the background using Orthanc + + if (backgroundCleared_) + { + Orthanc::ImageProcessing::Set(target, + backgroundColor_[0], + backgroundColor_[1], + backgroundColor_[2], + 255 /* alpha */); + } + } + + + void WidgetBase::ClearBackgroundCairo(OrthancStone::CairoContext& context) const + { + // Clear the background using Cairo + + if (IsBackgroundCleared()) + { + uint8_t red, green, blue; + GetBackgroundColor(red, green, blue); + + context.SetSourceColor(red, green, blue); + cairo_paint(context.GetObject()); + } + } + + + void WidgetBase::ClearBackgroundCairo(Orthanc::ImageAccessor& target) const + { + OrthancStone::CairoSurface surface(target, false /* no alpha */); + OrthancStone::CairoContext context(surface); + ClearBackgroundCairo(context); + } + + + void WidgetBase::UpdateStatusBar(const std::string& message) + { + if (statusBar_ != NULL) + { + statusBar_->SetMessage(message); + } + } + + + WidgetBase::WidgetBase(const std::string& name) : + parent_(NULL), + viewport_(NULL), + statusBar_(NULL), + backgroundCleared_(false), + transmitMouseOver_(false), + name_(name) + { + backgroundColor_[0] = 0; + backgroundColor_[1] = 0; + backgroundColor_[2] = 0; + } + + + void WidgetBase::SetViewport(WidgetViewport& viewport) + { + if (viewport_ != NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + viewport_ = &viewport; + } + } + + + void WidgetBase::SetBackgroundColor(uint8_t red, + uint8_t green, + uint8_t blue) + { + backgroundColor_[0] = red; + backgroundColor_[1] = green; + backgroundColor_[2] = blue; + } + + void WidgetBase::GetBackgroundColor(uint8_t& red, + uint8_t& green, + uint8_t& blue) const + { + red = backgroundColor_[0]; + green = backgroundColor_[1]; + blue = backgroundColor_[2]; + } + + + bool WidgetBase::Render(Orthanc::ImageAccessor& surface) + { +#if 0 + ClearBackgroundOrthanc(surface); +#else + ClearBackgroundCairo(surface); // Faster than Orthanc +#endif + + return true; + } + + + void WidgetBase::DoAnimation() + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/WidgetBase.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,117 @@ +/** + * 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 "IWidget.h" + +#include "../../Wrappers/CairoContext.h" +#include "../Viewport/WidgetViewport.h" + +namespace Deprecated +{ + class WidgetBase : public IWidget + { + private: + IWidget* parent_; + WidgetViewport* viewport_; + IStatusBar* statusBar_; + bool backgroundCleared_; + uint8_t backgroundColor_[3]; + bool transmitMouseOver_; + std::string name_; + + protected: + void ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const; + + void ClearBackgroundCairo(OrthancStone::CairoContext& context) const; + + void ClearBackgroundCairo(Orthanc::ImageAccessor& target) const; + + void UpdateStatusBar(const std::string& message); + + IStatusBar* GetStatusBar() const + { + return statusBar_; + } + + public: + WidgetBase(const std::string& name); + + virtual void FitContent() + { + } + + virtual void SetParent(IWidget& parent); + + virtual void SetViewport(WidgetViewport& viewport); + + void SetBackgroundCleared(bool clear) + { + backgroundCleared_ = clear; + } + + bool IsBackgroundCleared() const + { + return backgroundCleared_; + } + + void SetTransmitMouseOver(bool transmit) + { + transmitMouseOver_ = transmit; + } + + void SetBackgroundColor(uint8_t red, + uint8_t green, + uint8_t blue); + + void GetBackgroundColor(uint8_t& red, + uint8_t& green, + uint8_t& blue) const; + + virtual void SetStatusBar(IStatusBar& statusBar) + { + statusBar_ = &statusBar; + } + + virtual bool Render(Orthanc::ImageAccessor& surface); + + virtual bool HasAnimation() const + { + return false; + } + + virtual void DoAnimation(); + + virtual bool HasRenderMouseOver() + { + return transmitMouseOver_; + } + + virtual void NotifyContentChanged(); + + const std::string& GetName() const + { + return name_; + } + + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/WorldSceneWidget.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,231 @@ +/** + * 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 "WorldSceneWidget.h" + +#include "PanMouseTracker.h" +#include "ZoomMouseTracker.h" +#include "PanZoomMouseTracker.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +#include <math.h> +#include <memory> +#include <cassert> + +namespace Deprecated +{ + // this is an adapter between a IWorldSceneMouseTracker + // that is tracking a mouse in scene coordinates/mm and + // an IMouseTracker that is tracking a mouse + // in screen coordinates/pixels. + class WorldSceneWidget::SceneMouseTracker : public IMouseTracker + { + private: + ViewportGeometry view_; + std::auto_ptr<IWorldSceneMouseTracker> tracker_; + + public: + SceneMouseTracker(const ViewportGeometry& view, + IWorldSceneMouseTracker* tracker) : + view_(view), + tracker_(tracker) + { + if (tracker == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + virtual void Render(Orthanc::ImageAccessor& target) + { + if (tracker_->HasRender()) + { + OrthancStone::CairoSurface surface(target, false /* no alpha */); + OrthancStone::CairoContext context(surface); + view_.ApplyTransform(context); + tracker_->Render(context, view_.GetZoom()); + } + } + + virtual void MouseUp() + { + tracker_->MouseUp(); + } + + virtual void MouseMove(int x, + int y, + const std::vector<Touch>& displayTouches) + { + double sceneX, sceneY; + view_.MapPixelCenterToScene(sceneX, sceneY, x, y); + + 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(static_cast<float>(sx), static_cast<float>(sy))); + } + tracker_->MouseMove(x, y, sceneX, sceneY, displayTouches, sceneTouches); + } + }; + + + bool WorldSceneWidget::RenderCairo(OrthancStone::CairoContext& context) + { + view_.ApplyTransform(context); + return RenderScene(context, view_); + } + + + void WorldSceneWidget::RenderMouseOverCairo(OrthancStone::CairoContext& context, + int x, + int y) + { + ViewportGeometry view = GetView(); + view.ApplyTransform(context); + + double sceneX, sceneY; + view.MapPixelCenterToScene(sceneX, sceneY, x, y); + + if (interactor_) + { + interactor_->MouseOver(context, *this, view, sceneX, sceneY, GetStatusBar()); + } + } + + + void WorldSceneWidget::SetSceneExtent(ViewportGeometry& view) + { + view.SetSceneExtent(GetSceneExtent()); + } + + + void WorldSceneWidget::SetSize(unsigned int width, + unsigned int height) + { + CairoWidget::SetSize(width, height); + view_.SetDisplaySize(width, height); + } + + + void WorldSceneWidget::SetInteractor(IWorldSceneInteractor& interactor) + { + interactor_ = &interactor; + } + + + void WorldSceneWidget::FitContent() + { + SetSceneExtent(view_); + view_.FitContent(); + + NotifyContentChanged(); + } + + + void WorldSceneWidget::SetView(const ViewportGeometry& view) + { + view_ = view; + + NotifyContentChanged(); + } + + + IMouseTracker* WorldSceneWidget::CreateMouseTracker(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& touches) + { + double sceneX, sceneY; + view_.MapPixelCenterToScene(sceneX, sceneY, x, y); + + // asks the Widget Interactor to provide a mouse tracker + std::auto_ptr<IWorldSceneMouseTracker> tracker; + + if (interactor_) + { + tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar(), touches)); + } + + if (tracker.get() != NULL) + { + return new SceneMouseTracker(view_, tracker.release()); + } + else if (hasDefaultMouseEvents_) + { + printf("has default mouse events\n"); + if (touches.size() == 2) + { + printf("2 touches !\n"); + return new SceneMouseTracker(view_, new PanZoomMouseTracker(*this, touches)); + } + else + { + switch (button) + { + case OrthancStone::MouseButton_Middle: + return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y)); + + case OrthancStone::MouseButton_Right: + return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y)); + + default: + return NULL; + } + } + } + else + { + return NULL; + } + } + + + void WorldSceneWidget::MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers) + { + if (interactor_) + { + interactor_->MouseWheel(*this, direction, modifiers, GetStatusBar()); + } + } + + + void WorldSceneWidget::KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers) + { + if (interactor_) + { + interactor_->KeyPressed(*this, key, keyChar, modifiers, GetStatusBar()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/WorldSceneWidget.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,103 @@ +/** + * 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 "CairoWidget.h" +#include "IWorldSceneInteractor.h" + +#include "../Toolbox/ViewportGeometry.h" + +namespace Deprecated +{ + class WorldSceneWidget : public CairoWidget + { + private: + class SceneMouseTracker; + + ViewportGeometry view_; + IWorldSceneInteractor* interactor_; + bool hasDefaultMouseEvents_; + + protected: + virtual OrthancStone::Extent2D GetSceneExtent() = 0; + + virtual bool RenderScene(OrthancStone::CairoContext& context, + const ViewportGeometry& view) = 0; + + // From CairoWidget + virtual bool RenderCairo(OrthancStone::CairoContext& context); + + // From CairoWidget + virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context, + int x, + int y); + + void SetSceneExtent(ViewportGeometry& geometry); + + public: + WorldSceneWidget(const std::string& name) : + CairoWidget(name), + interactor_(NULL), + hasDefaultMouseEvents_(true) + { + } + + void SetDefaultMouseEvents(bool value) + { + hasDefaultMouseEvents_ = value; + } + + bool HasDefaultMouseEvents() const + { + return hasDefaultMouseEvents_; + } + + void SetInteractor(IWorldSceneInteractor& interactor); + + void SetView(const ViewportGeometry& view); + + const ViewportGeometry& GetView() const + { + return view_; + } + + virtual void SetSize(unsigned int width, + unsigned int height); + + virtual void FitContent(); + + virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers, + const std::vector<Touch>& touches); + + virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, + int x, + int y, + OrthancStone::KeyboardModifiers modifiers); + + virtual void KeyPressed(OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/ZoomMouseTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,110 @@ +/** + * 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 "ZoomMouseTracker.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +namespace Deprecated +{ + ZoomMouseTracker::ZoomMouseTracker(WorldSceneWidget& that, + int x, + int y) : + that_(that), + originalZoom_(that.GetView().GetZoom()), + downX_(x), + downY_(y) + { + that.GetView().MapPixelCenterToScene(centerX_, centerY_, x, 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 ZoomMouseTracker::Render(OrthancStone::CairoContext& context, + double zoom) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + + void ZoomMouseTracker::MouseMove(int displayX, + int displayY, + double x, + double y, + const std::vector<Touch>& displayTouches, + const std::vector<Touch>& sceneTouches) + { + static const double MIN_ZOOM = -4; + static const double MAX_ZOOM = 4; + + + if (!idle_) + { + double dy = static_cast<double>(displayY - downY_) * 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); + + ViewportGeometry view = that_.GetView(); + + view.SetZoom(z * originalZoom_); + + // Correct the pan so that the original click point is kept at + // the same location on the display + double panX, panY; + view.GetPan(panX, panY); + + int tx, ty; + view.MapSceneToDisplay(tx, ty, centerX_, centerY_); + view.SetPan(panX + static_cast<double>(downX_ - tx), + panY + static_cast<double>(downY_ - ty)); + + that_.SetView(view); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Widgets/ZoomMouseTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,64 @@ +/** + * 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 Deprecated +{ + class ZoomMouseTracker : public IWorldSceneMouseTracker + { + private: + WorldSceneWidget& that_; + double originalZoom_; + int downX_; + int downY_; + double centerX_; + double centerY_; + bool idle_; + double normalization_; + + public: + ZoomMouseTracker(WorldSceneWidget& that, + int x, + int y); + + virtual bool HasRender() const + { + return false; + } + + virtual void MouseUp() + { + } + + virtual void Render(OrthancStone::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); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/dev.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,958 @@ +/** + * 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 "Layers/FrameRenderer.h" +#include "Layers/LineLayerRenderer.h" +#include "Layers/SliceOutlineRenderer.h" +#include "Toolbox/DownloadStack.h" +#include "Toolbox/GeometryToolbox.h" +#include "Toolbox/OrthancSlicesLoader.h" +#include "Volumes/ISlicedVolume.h" +#include "Volumes/ImageBuffer3D.h" +#include "Widgets/SliceViewerWidget.h" + +#include <Core/Logging.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/OrthancException.h> + +#include <boost/math/special_functions/round.hpp> + + +namespace Deprecated +{ + // TODO: Handle errors while loading + class OrthancVolumeImage : + public ISlicedVolume, + public OrthancStone::IObserver + { + private: + OrthancSlicesLoader loader_; + std::auto_ptr<OrthancStone::ImageBuffer3D> image_; + std::auto_ptr<DownloadStack> downloadStack_; + bool computeRange_; + size_t pendingSlices_; + + void ScheduleSliceDownload() + { + assert(downloadStack_.get() != NULL); + + unsigned int slice; + if (downloadStack_->Pop(slice)) + { + loader_.ScheduleLoadSliceImage(slice, OrthancStone::SliceImageQuality_Jpeg90); + } + } + + + static bool IsCompatible(const Slice& a, + const Slice& b) + { + if (!OrthancStone::GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(), + b.GetGeometry().GetNormal())) + { + 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."; + return false; + } + + if (a.GetWidth() != b.GetWidth() || + a.GetHeight() != b.GetHeight()) + { + LOG(ERROR) << "The slices dimensions (width/height) are varying throughout the volume image"; + return false; + } + + if (!OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) || + !OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY())) + { + LOG(ERROR) << "The pixel spacing of the slices change across the volume image"; + return false; + } + + return true; + } + + + static double GetDistance(const Slice& a, + const Slice& b) + { + return fabs(a.GetGeometry().ProjectAlongNormal(a.GetGeometry().GetOrigin()) - + a.GetGeometry().ProjectAlongNormal(b.GetGeometry().GetOrigin())); + } + + + void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message) + { + assert(&message.GetOrigin() == &loader_); + + if (loader_.GetSlicesCount() == 0) + { + LOG(ERROR) << "Empty volume image"; + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); + return; + } + + for (size_t i = 1; i < loader_.GetSlicesCount(); i++) + { + if (!IsCompatible(loader_.GetSlice(0), loader_.GetSlice(i))) + { + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); + return; + } + } + + double spacingZ; + + if (loader_.GetSlicesCount() > 1) + { + spacingZ = GetDistance(loader_.GetSlice(0), loader_.GetSlice(1)); + } + else + { + // This is a volume with one single slice: Choose a dummy + // z-dimension for voxels + spacingZ = 1; + } + + for (size_t i = 1; i < loader_.GetSlicesCount(); i++) + { + if (!OrthancStone::LinearAlgebra::IsNear(spacingZ, GetDistance(loader_.GetSlice(i - 1), loader_.GetSlice(i)), + 0.001 /* this is expressed in mm */)) + { + LOG(ERROR) << "The distance between successive slices is not constant in a volume image"; + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); + return; + } + } + + unsigned int width = loader_.GetSlice(0).GetWidth(); + unsigned int height = loader_.GetSlice(0).GetHeight(); + Orthanc::PixelFormat format = loader_.GetSlice(0).GetConverter().GetExpectedPixelFormat(); + LOG(INFO) << "Creating a volume image of size " << width << "x" << height + << "x" << loader_.GetSlicesCount() << " in " << Orthanc::EnumerationToString(format); + + image_.reset(new OrthancStone::ImageBuffer3D(format, width, height, static_cast<unsigned int>(loader_.GetSlicesCount()), computeRange_)); + image_->GetGeometry().SetAxialGeometry(loader_.GetSlice(0).GetGeometry()); + image_->GetGeometry().SetVoxelDimensions(loader_.GetSlice(0).GetPixelSpacingX(), + loader_.GetSlice(0).GetPixelSpacingY(), spacingZ); + image_->Clear(); + + downloadStack_.reset(new DownloadStack(static_cast<unsigned int>(loader_.GetSlicesCount()))); + pendingSlices_ = loader_.GetSlicesCount(); + + for (unsigned int i = 0; i < 4; i++) // Limit to 4 simultaneous downloads + { + ScheduleSliceDownload(); + } + + // TODO Check the DicomFrameConverter are constant + + BroadcastMessage(ISlicedVolume::GeometryReadyMessage(*this)); + } + + + void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message) + { + assert(&message.GetOrigin() == &loader_); + + LOG(ERROR) << "Unable to download a volume image"; + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); + } + + + void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message) + { + assert(&message.GetOrigin() == &loader_); + + { + OrthancStone::ImageBuffer3D::SliceWriter writer(*image_, OrthancStone::VolumeProjection_Axial, message.GetSliceIndex()); + Orthanc::ImageProcessing::Copy(writer.GetAccessor(), message.GetImage()); + } + + BroadcastMessage(ISlicedVolume::SliceContentChangedMessage + (*this, message.GetSliceIndex(), message.GetSlice())); + + if (pendingSlices_ == 1) + { + BroadcastMessage(ISlicedVolume::VolumeReadyMessage(*this)); + pendingSlices_ = 0; + } + else if (pendingSlices_ > 1) + { + pendingSlices_ -= 1; + } + + ScheduleSliceDownload(); + } + + + void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message) + { + assert(&message.GetOrigin() == &loader_); + + LOG(ERROR) << "Cannot download slice " << message.GetSliceIndex() << " in a volume image"; + ScheduleSliceDownload(); + } + + + public: + OrthancVolumeImage(OrthancStone::MessageBroker& broker, + OrthancApiClient& orthanc, + bool computeRange) : + ISlicedVolume(broker), + IObserver(broker), + loader_(broker, orthanc), + computeRange_(computeRange), + pendingSlices_(0) + { + loader_.RegisterObserverCallback( + new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryReadyMessage> + (*this, &OrthancVolumeImage::OnSliceGeometryReady)); + + loader_.RegisterObserverCallback( + new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryErrorMessage> + (*this, &OrthancVolumeImage::OnSliceGeometryError)); + + loader_.RegisterObserverCallback( + new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageReadyMessage> + (*this, &OrthancVolumeImage::OnSliceImageReady)); + + loader_.RegisterObserverCallback( + new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageErrorMessage> + (*this, &OrthancVolumeImage::OnSliceImageError)); + } + + void ScheduleLoadSeries(const std::string& seriesId) + { + loader_.ScheduleLoadSeries(seriesId); + } + + void ScheduleLoadInstance(const std::string& instanceId) + { + loader_.ScheduleLoadInstance(instanceId); + } + + void ScheduleLoadFrame(const std::string& instanceId, + unsigned int frame) + { + loader_.ScheduleLoadFrame(instanceId, frame); + } + + virtual size_t GetSlicesCount() const + { + return loader_.GetSlicesCount(); + } + + virtual const Slice& GetSlice(size_t index) const + { + return loader_.GetSlice(index); + } + + OrthancStone::ImageBuffer3D& GetImage() const + { + if (image_.get() == NULL) + { + // The geometry is not ready yet + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *image_; + } + } + + bool FitWindowingToRange(RenderStyle& style, + const DicomFrameConverter& converter) const + { + if (image_.get() == NULL) + { + return false; + } + else + { + return image_->FitWindowingToRange(style, converter); + } + } + }; + + + class VolumeImageGeometry + { + private: + unsigned int width_; + unsigned int height_; + size_t depth_; + double pixelSpacingX_; + double pixelSpacingY_; + double sliceThickness_; + OrthancStone::CoordinateSystem3D reference_; + DicomFrameConverter converter_; + + double ComputeAxialThickness(const OrthancVolumeImage& volume) const + { + double thickness; + + size_t n = volume.GetSlicesCount(); + if (n > 1) + { + const Slice& a = volume.GetSlice(0); + const Slice& b = volume.GetSlice(n - 1); + thickness = ((reference_.ProjectAlongNormal(b.GetGeometry().GetOrigin()) - + reference_.ProjectAlongNormal(a.GetGeometry().GetOrigin())) / + (static_cast<double>(n) - 1.0)); + } + else + { + thickness = volume.GetSlice(0).GetThickness(); + } + + if (thickness <= 0) + { + // The slices should have been sorted with increasing Z + // (along the normal) by the OrthancSlicesLoader + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + else + { + return thickness; + } + } + + void SetupAxial(const OrthancVolumeImage& volume) + { + const Slice& axial = volume.GetSlice(0); + + width_ = axial.GetWidth(); + height_ = axial.GetHeight(); + depth_ = volume.GetSlicesCount(); + + pixelSpacingX_ = axial.GetPixelSpacingX(); + pixelSpacingY_ = axial.GetPixelSpacingY(); + sliceThickness_ = ComputeAxialThickness(volume); + + reference_ = axial.GetGeometry(); + } + + void SetupCoronal(const OrthancVolumeImage& volume) + { + const Slice& axial = volume.GetSlice(0); + double axialThickness = ComputeAxialThickness(volume); + + width_ = axial.GetWidth(); + height_ = static_cast<unsigned int>(volume.GetSlicesCount()); + depth_ = axial.GetHeight(); + + pixelSpacingX_ = axial.GetPixelSpacingX(); + pixelSpacingY_ = axialThickness; + sliceThickness_ = axial.GetPixelSpacingY(); + + OrthancStone::Vector origin = axial.GetGeometry().GetOrigin(); + origin += (static_cast<double>(volume.GetSlicesCount() - 1) * + axialThickness * axial.GetGeometry().GetNormal()); + + reference_ = OrthancStone::CoordinateSystem3D(origin, + axial.GetGeometry().GetAxisX(), + - axial.GetGeometry().GetNormal()); + } + + void SetupSagittal(const OrthancVolumeImage& volume) + { + const Slice& axial = volume.GetSlice(0); + double axialThickness = ComputeAxialThickness(volume); + + width_ = axial.GetHeight(); + height_ = static_cast<unsigned int>(volume.GetSlicesCount()); + depth_ = axial.GetWidth(); + + pixelSpacingX_ = axial.GetPixelSpacingY(); + pixelSpacingY_ = axialThickness; + sliceThickness_ = axial.GetPixelSpacingX(); + + OrthancStone::Vector origin = axial.GetGeometry().GetOrigin(); + origin += (static_cast<double>(volume.GetSlicesCount() - 1) * + axialThickness * axial.GetGeometry().GetNormal()); + + reference_ = OrthancStone::CoordinateSystem3D(origin, + axial.GetGeometry().GetAxisY(), + axial.GetGeometry().GetNormal()); + } + + public: + VolumeImageGeometry(const OrthancVolumeImage& volume, + OrthancStone::VolumeProjection projection) + { + if (volume.GetSlicesCount() == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + converter_ = volume.GetSlice(0).GetConverter(); + + switch (projection) + { + case OrthancStone::VolumeProjection_Axial: + SetupAxial(volume); + break; + + case OrthancStone::VolumeProjection_Coronal: + SetupCoronal(volume); + break; + + case OrthancStone::VolumeProjection_Sagittal: + SetupSagittal(volume); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + size_t GetSlicesCount() const + { + return depth_; + } + + const OrthancStone::Vector& GetNormal() const + { + return reference_.GetNormal(); + } + + bool LookupSlice(size_t& index, + const OrthancStone::CoordinateSystem3D& slice) const + { + bool opposite; + if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, + reference_.GetNormal(), + slice.GetNormal())) + { + 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_)) + { + return false; + } + else + { + index = static_cast<size_t>(s); + return true; + } + } + + Slice* GetSlice(size_t slice) const + { + if (slice >= depth_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + OrthancStone::CoordinateSystem3D origin(reference_.GetOrigin() + + static_cast<double>(slice) * sliceThickness_ * reference_.GetNormal(), + reference_.GetAxisX(), + reference_.GetAxisY()); + + return new Slice(origin, pixelSpacingX_, pixelSpacingY_, sliceThickness_, + width_, height_, converter_); + } + } + }; + + + + class VolumeImageMPRSlicer : + public IVolumeSlicer, + public OrthancStone::IObserver + { + private: + class RendererFactory : public LayerReadyMessage::IRendererFactory + { + private: + const Orthanc::ImageAccessor& frame_; + const Slice& slice_; + bool isFullQuality_; + + public: + RendererFactory(const Orthanc::ImageAccessor& frame, + const Slice& slice, + bool isFullQuality) : + frame_(frame), + slice_(slice), + isFullQuality_(isFullQuality) + { + } + + virtual ILayerRenderer* CreateRenderer() const + { + return FrameRenderer::CreateRenderer(frame_, slice_, isFullQuality_); + } + }; + + + OrthancVolumeImage& volume_; + std::auto_ptr<VolumeImageGeometry> axialGeometry_; + 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_, OrthancStone::VolumeProjection_Axial)); + coronalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Coronal)); + sagittalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Sagittal)); + + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + } + + void OnGeometryError(const ISlicedVolume::GeometryErrorMessage& message) + { + assert(&message.GetOrigin() == &volume_); + + BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); + } + + void OnContentChanged(const ISlicedVolume::ContentChangedMessage& message) + { + assert(&message.GetOrigin() == &volume_); + + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); + } + + void OnSliceContentChanged(const ISlicedVolume::SliceContentChangedMessage& message) + { + assert(&message.GetOrigin() == &volume_); + + //IVolumeSlicer::OnSliceContentChange(slice); + + // TODO Improve this? + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); + } + + const VolumeImageGeometry& GetProjectionGeometry(OrthancStone::VolumeProjection projection) + { + if (!IsGeometryReady()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + switch (projection) + { + case OrthancStone::VolumeProjection_Axial: + return *axialGeometry_; + + case OrthancStone::VolumeProjection_Sagittal: + return *sagittalGeometry_; + + case OrthancStone::VolumeProjection_Coronal: + return *coronalGeometry_; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + bool DetectProjection(OrthancStone::VolumeProjection& projection, + const OrthancStone::CoordinateSystem3D& viewportSlice) + { + bool isOpposite; // Ignored + + if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, + viewportSlice.GetNormal(), + axialGeometry_->GetNormal())) + { + projection = OrthancStone::VolumeProjection_Axial; + return true; + } + else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, + viewportSlice.GetNormal(), + sagittalGeometry_->GetNormal())) + { + projection = OrthancStone::VolumeProjection_Sagittal; + return true; + } + else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, + viewportSlice.GetNormal(), + coronalGeometry_->GetNormal())) + { + projection = OrthancStone::VolumeProjection_Coronal; + return true; + } + else + { + return false; + } + } + + + public: + VolumeImageMPRSlicer(OrthancStone::MessageBroker& broker, + OrthancVolumeImage& volume) : + IVolumeSlicer(broker), + IObserver(broker), + volume_(volume) + { + volume_.RegisterObserverCallback( + new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryReadyMessage> + (*this, &VolumeImageMPRSlicer::OnGeometryReady)); + + volume_.RegisterObserverCallback( + new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryErrorMessage> + (*this, &VolumeImageMPRSlicer::OnGeometryError)); + + volume_.RegisterObserverCallback( + new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::ContentChangedMessage> + (*this, &VolumeImageMPRSlicer::OnContentChanged)); + + volume_.RegisterObserverCallback( + new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::SliceContentChangedMessage> + (*this, &VolumeImageMPRSlicer::OnSliceContentChanged)); + } + + virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, + const OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE + { + OrthancStone::VolumeProjection projection; + + if (!IsGeometryReady() || + !DetectProjection(projection, viewportSlice)) + { + return false; + } + else + { + // As the slices of the volumic image are arranged in a box, + // 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 OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE + { + OrthancStone::VolumeProjection projection; + + if (IsGeometryReady() && + DetectProjection(projection, viewportSlice)) + { + const VolumeImageGeometry& geometry = GetProjectionGeometry(projection); + + size_t closest; + + if (geometry.LookupSlice(closest, viewportSlice)) + { + bool isFullQuality = true; // TODO + + std::auto_ptr<Orthanc::Image> frame; + + { + OrthancStone::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())); + } + + std::auto_ptr<Slice> slice(geometry.GetSlice(closest)); + + RendererFactory factory(*frame, *slice, isFullQuality); + + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice->GetGeometry())); + return; + } + } + + // Error + OrthancStone::CoordinateSystem3D slice; + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, slice)); + } + }; + + + class VolumeImageInteractor : + public IWorldSceneInteractor, + public OrthancStone::IObserver + { + private: + SliceViewerWidget& widget_; + OrthancStone::VolumeProjection projection_; + std::auto_ptr<VolumeImageGeometry> slices_; + size_t slice_; + + protected: + void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message) + { + if (slices_.get() == NULL) + { + const OrthancVolumeImage& image = + dynamic_cast<const OrthancVolumeImage&>(message.GetOrigin()); + + slices_.reset(new VolumeImageGeometry(image, projection_)); + SetSlice(slices_->GetSlicesCount() / 2); + + widget_.FitContent(); + } + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + OrthancStone::MouseButton button, + OrthancStone::KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + IStatusBar* statusBar, + const std::vector<Touch>& touches) ORTHANC_OVERRIDE + { + return NULL; + } + + virtual void MouseOver(OrthancStone::CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) ORTHANC_OVERRIDE + { + } + + virtual void MouseWheel(WorldSceneWidget& widget, + OrthancStone::MouseWheelDirection direction, + OrthancStone::KeyboardModifiers modifiers, + IStatusBar* statusBar) ORTHANC_OVERRIDE + { + int scale = (modifiers & OrthancStone::KeyboardModifiers_Control ? 10 : 1); + + switch (direction) + { + case OrthancStone::MouseWheelDirection_Up: + OffsetSlice(-scale); + break; + + case OrthancStone::MouseWheelDirection_Down: + OffsetSlice(scale); + break; + + default: + break; + } + } + + virtual void KeyPressed(WorldSceneWidget& widget, + OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers, + IStatusBar* statusBar) ORTHANC_OVERRIDE + { + switch (keyChar) + { + case 's': + widget.FitContent(); + break; + + default: + break; + } + } + + public: + VolumeImageInteractor(OrthancStone::MessageBroker& broker, + OrthancVolumeImage& volume, + SliceViewerWidget& widget, + OrthancStone::VolumeProjection projection) : + IObserver(broker), + widget_(widget), + projection_(projection) + { + widget.SetInteractor(*this); + + volume.RegisterObserverCallback( + new OrthancStone::Callable<VolumeImageInteractor, ISlicedVolume::GeometryReadyMessage> + (*this, &VolumeImageInteractor::OnGeometryReady)); + } + + bool IsGeometryReady() const + { + return slices_.get() != NULL; + } + + size_t GetSlicesCount() const + { + if (slices_.get() == NULL) + { + return 0; + } + else + { + return slices_->GetSlicesCount(); + } + } + + void OffsetSlice(int offset) + { + if (slices_.get() != NULL) + { + int slice = static_cast<int>(slice_) + offset; + + if (slice < 0) + { + slice = 0; + } + + if (slice >= static_cast<int>(slices_->GetSlicesCount())) + { + slice = static_cast<unsigned int>(slices_->GetSlicesCount()) - 1; + } + + if (slice != static_cast<int>(slice_)) + { + SetSlice(slice); + } + } + } + + void SetSlice(size_t slice) + { + if (slices_.get() != NULL) + { + slice_ = slice; + + std::auto_ptr<Slice> tmp(slices_->GetSlice(slice_)); + widget_.SetSlice(tmp->GetGeometry()); + } + } + }; + + + + class ReferenceLineSource : public IVolumeSlicer + { + private: + class RendererFactory : public LayerReadyMessage::IRendererFactory + { + private: + double x1_; + double y1_; + double x2_; + double y2_; + const OrthancStone::CoordinateSystem3D& slice_; + + public: + RendererFactory(double x1, + double y1, + double x2, + double y2, + const OrthancStone::CoordinateSystem3D& slice) : + x1_(x1), + y1_(y1), + x2_(x2), + y2_(y2), + slice_(slice) + { + } + + virtual ILayerRenderer* CreateRenderer() const + { + return new LineLayerRenderer(x1_, y1_, x2_, y2_, slice_); + } + }; + + SliceViewerWidget& otherPlane_; + + public: + ReferenceLineSource(OrthancStone::MessageBroker& broker, + SliceViewerWidget& otherPlane) : + IVolumeSlicer(broker), + otherPlane_(otherPlane) + { + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + } + + virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, + const OrthancStone::CoordinateSystem3D& viewportSlice) + { + return false; + } + + virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) + { + Slice reference(viewportSlice, 0.001); + + OrthancStone::Vector p, d; + + const OrthancStone::CoordinateSystem3D& slice = otherPlane_.GetSlice(); + + // Compute the line of intersection between the two slices + if (!OrthancStone::GeometryToolbox::IntersectTwoPlanes(p, d, + slice.GetOrigin(), slice.GetNormal(), + viewportSlice.GetOrigin(), viewportSlice.GetNormal())) + { + // The two slice are parallel, don't try and display the intersection + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); + } + else + { + double x1, y1, x2, y2; + viewportSlice.ProjectPoint(x1, y1, p); + viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d); + + const OrthancStone::Extent2D extent = otherPlane_.GetSceneExtent(); + + if (OrthancStone::GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, + x1, y1, x2, y2, + extent.GetX1(), extent.GetY1(), + extent.GetX2(), extent.GetY2())) + { + RendererFactory factory(x1, y1, x2, y2, slice); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, reference.GetGeometry())); + } + else + { + // Error: Parallel slices + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); + } + } + } + }; +}
--- a/Framework/Layers/CircleMeasureTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +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 "CircleMeasureTracker.h" - -#include <stdio.h> -#include <boost/math/constants/constants.hpp> - -namespace Deprecated -{ - CircleMeasureTracker::CircleMeasureTracker(IStatusBar* statusBar, - const OrthancStone::CoordinateSystem3D& slice, - double x, - double y, - uint8_t red, - uint8_t green, - uint8_t blue, - const Orthanc::Font& font) : - statusBar_(statusBar), - slice_(slice), - x1_(x), - y1_(y), - x2_(x), - y2_(y), - font_(font) - { - color_[0] = red; - color_[1] = green; - color_[2] = blue; - } - - - void CircleMeasureTracker::Render(OrthancStone::CairoContext& context, - double zoom) - { - double x = (x1_ + x2_) / 2.0; - double y = (y1_ + y2_) / 2.0; - - OrthancStone::Vector tmp; - OrthancStone::LinearAlgebra::AssignVector(tmp, x2_ - x1_, y2_ - y1_); - double r = boost::numeric::ublas::norm_2(tmp) / 2.0; - - context.SetSourceColor(color_[0], color_[1], color_[2]); - - cairo_t* cr = context.GetObject(); - cairo_save(cr); - cairo_set_line_width(cr, 2.0 / zoom); - cairo_translate(cr, x, y); - cairo_arc(cr, 0, 0, r, 0, 2.0 * boost::math::constants::pi<double>()); - cairo_stroke_preserve(cr); - cairo_stroke(cr); - cairo_restore(cr); - - context.DrawText(font_, FormatRadius(), x, y, OrthancStone::BitmapAnchor_Center); - } - - - double CircleMeasureTracker::GetRadius() const // In millimeters - { - OrthancStone::Vector a = slice_.MapSliceToWorldCoordinates(x1_, y1_); - OrthancStone::Vector b = slice_.MapSliceToWorldCoordinates(x2_, y2_); - return boost::numeric::ublas::norm_2(b - a) / 2.0; - } - - - std::string CircleMeasureTracker::FormatRadius() const - { - char buf[64]; - sprintf(buf, "%0.01f cm", GetRadius() / 10.0); - return buf; - } - - void CircleMeasureTracker::MouseMove(int displayX, - int displayY, - double x, - double y, - const std::vector<Touch>& displayTouches, - const std::vector<Touch>& sceneTouches) - { - x2_ = x; - y2_ = y; - - if (statusBar_ != NULL) - { - statusBar_->SetMessage("Circle radius: " + FormatRadius()); - } - } -}
--- a/Framework/Layers/CircleMeasureTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +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 "../Widgets/IWorldSceneMouseTracker.h" - -#include "../Viewport/IStatusBar.h" -#include "../Toolbox/CoordinateSystem3D.h" - -#include <Core/Images/Font.h> - -namespace Deprecated -{ - class CircleMeasureTracker : public IWorldSceneMouseTracker - { - private: - IStatusBar* statusBar_; - OrthancStone::CoordinateSystem3D slice_; - double x1_; - double y1_; - double x2_; - double y2_; - uint8_t color_[3]; - const Orthanc::Font& font_; - - public: - CircleMeasureTracker(IStatusBar* statusBar, - const OrthancStone::CoordinateSystem3D& slice, - double x, - double y, - uint8_t red, - uint8_t green, - uint8_t blue, - const Orthanc::Font& font); - - virtual bool HasRender() const - { - return true; - } - - virtual void Render(OrthancStone::CairoContext& context, - double zoom); - - double GetRadius() const; // In millimeters - - std::string FormatRadius() const; - - virtual void MouseUp() - { - // Possibly create a new landmark "volume" with the circle in subclasses - } - - virtual void MouseMove(int displayX, - int displayY, - double x, - double y, - const std::vector<Touch>& displayTouches, - const std::vector<Touch>& sceneTouches); - }; -}
--- a/Framework/Layers/ColorFrameRenderer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +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 "ColorFrameRenderer.h" - -#include <Core/OrthancException.h> -#include <Core/Images/Image.h> -#include <Core/Images/ImageProcessing.h> - -namespace Deprecated -{ - OrthancStone::CairoSurface* ColorFrameRenderer::GenerateDisplay(const RenderStyle& style) - { - std::auto_ptr<OrthancStone::CairoSurface> display - (new OrthancStone::CairoSurface(frame_->GetWidth(), frame_->GetHeight(), false /* no alpha */)); - - Orthanc::ImageAccessor target; - display->GetWriteableAccessor(target); - - Orthanc::ImageProcessing::Convert(target, *frame_); - - return display.release(); - } - - - ColorFrameRenderer::ColorFrameRenderer(const Orthanc::ImageAccessor& frame, - const OrthancStone::CoordinateSystem3D& framePlane, - double pixelSpacingX, - double pixelSpacingY, - bool isFullQuality) : - FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality), - frame_(Orthanc::Image::Clone(frame)) - { - if (frame_.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - if (frame_->GetFormat() != Orthanc::PixelFormat_RGB24) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - } -}
--- a/Framework/Layers/ColorFrameRenderer.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +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 "FrameRenderer.h" - -namespace Deprecated -{ - class ColorFrameRenderer : public FrameRenderer - { - private: - std::auto_ptr<Orthanc::ImageAccessor> frame_; // In RGB24 - - protected: - virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style); - - public: - ColorFrameRenderer(const Orthanc::ImageAccessor& frame, - const OrthancStone::CoordinateSystem3D& framePlane, - double pixelSpacingX, - double pixelSpacingY, - bool isFullQuality); - }; -}
--- a/Framework/Layers/DicomSeriesVolumeSlicer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +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 "DicomSeriesVolumeSlicer.h" - -#include "FrameRenderer.h" -#include "../Toolbox/DicomFrameConverter.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> - -#include <boost/lexical_cast.hpp> - -namespace Deprecated -{ - - void DicomSeriesVolumeSlicer::OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message) - { - if (message.GetOrigin().GetSlicesCount() > 0) - { - BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); - } - else - { - BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); - } - } - - void DicomSeriesVolumeSlicer::OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message) - { - BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); - } - - - class DicomSeriesVolumeSlicer::RendererFactory : public LayerReadyMessage::IRendererFactory - { - private: - const OrthancSlicesLoader::SliceImageReadyMessage& message_; - - public: - RendererFactory(const OrthancSlicesLoader::SliceImageReadyMessage& message) : - message_(message) - { - } - - virtual ILayerRenderer* CreateRenderer() const - { - bool isFull = (message_.GetEffectiveQuality() == OrthancStone::SliceImageQuality_FullPng || - message_.GetEffectiveQuality() == OrthancStone::SliceImageQuality_FullPam); - - return FrameRenderer::CreateRenderer(message_.GetImage(), message_.GetSlice(), isFull); - } - }; - - void DicomSeriesVolumeSlicer::OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message) - { - // first notify that the pixel data of the frame is ready (targeted to, i.e: an image cache) - BroadcastMessage(FrameReadyMessage(*this, message.GetImage(), - message.GetEffectiveQuality(), message.GetSlice())); - - // then notify that the layer is ready for rendering - RendererFactory factory(message); - BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, message.GetSlice().GetGeometry())); - } - - void DicomSeriesVolumeSlicer::OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message) - { - BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, message.GetSlice().GetGeometry())); - } - - - DicomSeriesVolumeSlicer::DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc) : - IVolumeSlicer(broker), - IObserver(broker), - loader_(broker, orthanc), - quality_(OrthancStone::SliceImageQuality_FullPng) - { - loader_.RegisterObserverCallback( - new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryReadyMessage> - (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryErrorMessage> - (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageReadyMessage> - (*this, &DicomSeriesVolumeSlicer::OnSliceImageReady)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageErrorMessage> - (*this, &DicomSeriesVolumeSlicer::OnSliceImageError)); - } - - - void DicomSeriesVolumeSlicer::LoadSeries(const std::string& seriesId) - { - loader_.ScheduleLoadSeries(seriesId); - } - - - void DicomSeriesVolumeSlicer::LoadInstance(const std::string& instanceId) - { - loader_.ScheduleLoadInstance(instanceId); - } - - - void DicomSeriesVolumeSlicer::LoadFrame(const std::string& instanceId, - unsigned int frame) - { - loader_.ScheduleLoadFrame(instanceId, frame); - } - - - bool DicomSeriesVolumeSlicer::GetExtent(std::vector<OrthancStone::Vector>& points, - const OrthancStone::CoordinateSystem3D& viewportSlice) - { - size_t index; - - if (loader_.IsGeometryReady() && - loader_.LookupSlice(index, viewportSlice)) - { - loader_.GetSlice(index).GetExtent(points); - return true; - } - else - { - return false; - } - } - - - void DicomSeriesVolumeSlicer::ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) - { - size_t index; - - if (loader_.IsGeometryReady() && - loader_.LookupSlice(index, viewportSlice)) - { - loader_.ScheduleLoadSliceImage(index, quality_); - } - } -}
--- a/Framework/Layers/DicomSeriesVolumeSlicer.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +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 "IVolumeSlicer.h" -#include "../Toolbox/IWebService.h" -#include "../Toolbox/OrthancSlicesLoader.h" -#include "../Toolbox/OrthancApiClient.h" - -namespace Deprecated -{ - // this class is in charge of loading a Frame. - // once it's been loaded (first the geometry and then the image), - // messages are sent to observers so they can use it - class DicomSeriesVolumeSlicer : - public IVolumeSlicer, - public OrthancStone::IObserver - //private OrthancSlicesLoader::ISliceLoaderObserver - { - public: - // TODO: Add "frame" and "instanceId" - class FrameReadyMessage : public OrthancStone::OriginMessage<DicomSeriesVolumeSlicer> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const Orthanc::ImageAccessor& frame_; - OrthancStone::SliceImageQuality imageQuality_; - const Slice& slice_; - - public: - FrameReadyMessage(DicomSeriesVolumeSlicer& origin, - const Orthanc::ImageAccessor& frame, - OrthancStone::SliceImageQuality imageQuality, - const Slice& slice) : - OriginMessage(origin), - frame_(frame), - imageQuality_(imageQuality), - slice_(slice) - { - } - - const Orthanc::ImageAccessor& GetFrame() const - { - return frame_; - } - - OrthancStone::SliceImageQuality GetImageQuality() const - { - return imageQuality_; - } - - const Slice& GetSlice() const - { - return slice_; - } - }; - - - private: - class RendererFactory; - - OrthancSlicesLoader loader_; - OrthancStone::SliceImageQuality quality_; - - public: - DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc); - - void LoadSeries(const std::string& seriesId); - - void LoadInstance(const std::string& instanceId); - - void LoadFrame(const std::string& instanceId, - unsigned int frame); - - void SetImageQuality(OrthancStone::SliceImageQuality quality) - { - quality_ = quality; - } - - OrthancStone::SliceImageQuality GetImageQuality() const - { - return quality_; - } - - size_t GetSlicesCount() const - { - return loader_.GetSlicesCount(); - } - - const Slice& GetSlice(size_t slice) const - { - return loader_.GetSlice(slice); - } - - virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, - const OrthancStone::CoordinateSystem3D& viewportSlice); - - virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice); - -protected: - void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message); - void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message); - void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message); - void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message); - }; -}
--- a/Framework/Layers/DicomStructureSetSlicer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,170 +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 "DicomStructureSetSlicer.h" - -namespace Deprecated -{ - class DicomStructureSetSlicer::Renderer : public ILayerRenderer - { - private: - class Structure - { - private: - bool visible_; - uint8_t red_; - uint8_t green_; - uint8_t blue_; - std::string name_; - std::vector< std::vector<OrthancStone::DicomStructureSet::PolygonPoint> > polygons_; - - public: - Structure(OrthancStone::DicomStructureSet& structureSet, - const OrthancStone::CoordinateSystem3D& plane, - size_t index) : - name_(structureSet.GetStructureName(index)) - { - structureSet.GetStructureColor(red_, green_, blue_, index); - visible_ = structureSet.ProjectStructure(polygons_, index, plane); - } - - void Render(OrthancStone::CairoContext& context) - { - if (visible_) - { - cairo_t* cr = context.GetObject(); - - context.SetSourceColor(red_, green_, blue_); - - for (size_t i = 0; i < polygons_.size(); i++) - { - cairo_move_to(cr, polygons_[i][0].first, polygons_[i][0].second); - - for (size_t j = 1; j < polygons_[i].size(); j++) - { - cairo_line_to(cr, polygons_[i][j].first, polygons_[i][j].second); - } - - cairo_line_to(cr, polygons_[i][0].first, polygons_[i][0].second); - cairo_stroke(cr); - } - } - } - }; - - typedef std::list<Structure*> Structures; - - OrthancStone::CoordinateSystem3D plane_; - Structures structures_; - - public: - Renderer(OrthancStone::DicomStructureSet& structureSet, - const OrthancStone::CoordinateSystem3D& plane) : - plane_(plane) - { - for (size_t k = 0; k < structureSet.GetStructureCount(); k++) - { - structures_.push_back(new Structure(structureSet, plane, k)); - } - } - - virtual ~Renderer() - { - for (Structures::iterator it = structures_.begin(); - it != structures_.end(); ++it) - { - delete *it; - } - } - - virtual bool RenderLayer(OrthancStone::CairoContext& context, - const ViewportGeometry& view) - { - cairo_set_line_width(context.GetObject(), 2.0f / view.GetZoom()); - - for (Structures::const_iterator it = structures_.begin(); - it != structures_.end(); ++it) - { - assert(*it != NULL); - (*it)->Render(context); - } - - return true; - } - - virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane() - { - return plane_; - } - - virtual void SetLayerStyle(const RenderStyle& style) - { - } - - virtual bool IsFullQuality() - { - return true; - } - }; - - - class DicomStructureSetSlicer::RendererFactory : public LayerReadyMessage::IRendererFactory - { - private: - OrthancStone::DicomStructureSet& structureSet_; - const OrthancStone::CoordinateSystem3D& plane_; - - public: - RendererFactory(OrthancStone::DicomStructureSet& structureSet, - const OrthancStone::CoordinateSystem3D& plane) : - structureSet_(structureSet), - plane_(plane) - { - } - - virtual ILayerRenderer* CreateRenderer() const - { - return new Renderer(structureSet_, plane_); - } - }; - - - DicomStructureSetSlicer::DicomStructureSetSlicer(OrthancStone::MessageBroker& broker, - StructureSetLoader& loader) : - IVolumeSlicer(broker), - IObserver(broker), - loader_(loader) - { - loader_.RegisterObserverCallback( - new OrthancStone::Callable<DicomStructureSetSlicer, StructureSetLoader::ContentChangedMessage> - (*this, &DicomStructureSetSlicer::OnStructureSetLoaded)); - } - - - void DicomStructureSetSlicer::ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportPlane) - { - if (loader_.HasStructureSet()) - { - RendererFactory factory(loader_.GetStructureSet(), viewportPlane); - BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, viewportPlane)); - } - } -}
--- a/Framework/Layers/DicomStructureSetSlicer.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +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 "IVolumeSlicer.h" -#include "../Volumes/StructureSetLoader.h" - -namespace Deprecated -{ - class DicomStructureSetSlicer : - public IVolumeSlicer, - public OrthancStone::IObserver - { - private: - class Renderer; - class RendererFactory; - - StructureSetLoader& loader_; - - void OnStructureSetLoaded(const IVolumeLoader::ContentChangedMessage& message) - { - BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); - } - - public: - DicomStructureSetSlicer(OrthancStone::MessageBroker& broker, - StructureSetLoader& loader); - - virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, - const OrthancStone::CoordinateSystem3D& viewportPlane) - { - return false; - } - - virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportPlane); - }; -}
--- a/Framework/Layers/FrameRenderer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +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 "FrameRenderer.h" - -#include "GrayscaleFrameRenderer.h" -#include "ColorFrameRenderer.h" - -#include <Core/OrthancException.h> - -namespace Deprecated -{ - FrameRenderer::FrameRenderer(const OrthancStone::CoordinateSystem3D& framePlane, - double pixelSpacingX, - double pixelSpacingY, - bool isFullQuality) : - framePlane_(framePlane), - pixelSpacingX_(pixelSpacingX), - pixelSpacingY_(pixelSpacingY), - isFullQuality_(isFullQuality) - { - } - - - bool FrameRenderer::RenderLayer(OrthancStone::CairoContext& context, - const ViewportGeometry& view) - { - if (!style_.visible_) - { - return true; - } - - if (display_.get() == NULL) - { - display_.reset(GenerateDisplay(style_)); - } - - assert(display_.get() != NULL); - - cairo_t *cr = context.GetObject(); - - cairo_save(cr); - - cairo_matrix_t transform; - cairo_matrix_init_identity(&transform); - cairo_matrix_scale(&transform, pixelSpacingX_, pixelSpacingY_); - cairo_matrix_translate(&transform, -0.5, -0.5); - cairo_transform(cr, &transform); - - //cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - cairo_set_source_surface(cr, display_->GetObject(), 0, 0); - - switch (style_.interpolation_) - { - case OrthancStone::ImageInterpolation_Nearest: - cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); - break; - - case OrthancStone::ImageInterpolation_Bilinear: - cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - cairo_paint_with_alpha(cr, style_.alpha_); - - if (style_.drawGrid_) - { - context.SetSourceColor(style_.drawColor_); - cairo_set_line_width(cr, 0.5 / view.GetZoom()); - - for (unsigned int x = 0; x <= display_->GetWidth(); x++) - { - cairo_move_to(cr, x, 0); - cairo_line_to(cr, x, display_->GetHeight()); - } - - for (unsigned int y = 0; y <= display_->GetHeight(); y++) - { - cairo_move_to(cr, 0, y); - cairo_line_to(cr, display_->GetWidth(), y); - } - - cairo_stroke(cr); - } - - cairo_restore(cr); - - return true; - } - - - void FrameRenderer::SetLayerStyle(const RenderStyle& style) - { - style_ = style; - display_.reset(NULL); - } - - - ILayerRenderer* FrameRenderer::CreateRenderer(const Orthanc::ImageAccessor& frame, - const Deprecated::Slice& framePlane, - bool isFullQuality) - { - if (frame.GetFormat() == Orthanc::PixelFormat_RGB24) - { - return new ColorFrameRenderer(frame, - framePlane.GetGeometry(), - framePlane.GetPixelSpacingX(), - framePlane.GetPixelSpacingY(), isFullQuality); - } - else - { - return new GrayscaleFrameRenderer(frame, - framePlane.GetConverter(), - framePlane.GetGeometry(), - framePlane.GetPixelSpacingX(), - framePlane.GetPixelSpacingY(), isFullQuality); - } - } -}
--- a/Framework/Layers/FrameRenderer.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +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 "ILayerRenderer.h" - -#include "../Toolbox/Slice.h" - -namespace Deprecated -{ - class FrameRenderer : public ILayerRenderer - { - private: - OrthancStone::CoordinateSystem3D framePlane_; - double pixelSpacingX_; - double pixelSpacingY_; - RenderStyle style_; - bool isFullQuality_; - std::auto_ptr<OrthancStone::CairoSurface> display_; - - protected: - virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style) = 0; - - public: - FrameRenderer(const OrthancStone::CoordinateSystem3D& framePlane, - double pixelSpacingX, - double pixelSpacingY, - bool isFullQuality); - - virtual bool RenderLayer(OrthancStone::CairoContext& context, - const ViewportGeometry& view); - - virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane() - { - return framePlane_; - } - - virtual void SetLayerStyle(const RenderStyle& style); - - virtual bool IsFullQuality() - { - return isFullQuality_; - } - - // TODO: Avoid cloning the "frame" - static ILayerRenderer* CreateRenderer(const Orthanc::ImageAccessor& frame, - const Deprecated::Slice& framePlane, - bool isFullQuality); - }; -}
--- a/Framework/Layers/GrayscaleFrameRenderer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +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 "GrayscaleFrameRenderer.h" - -#include <Core/Images/Image.h> -#include <Core/OrthancException.h> - -namespace Deprecated -{ - OrthancStone::CairoSurface* GrayscaleFrameRenderer::GenerateDisplay(const RenderStyle& style) - { - assert(frame_->GetFormat() == Orthanc::PixelFormat_Float32); - - std::auto_ptr<OrthancStone::CairoSurface> result; - - float windowCenter, windowWidth; - style.ComputeWindowing(windowCenter, windowWidth, - defaultWindowCenter_, defaultWindowWidth_); - - float x0 = windowCenter - windowWidth / 2.0f; - float x1 = windowCenter + windowWidth / 2.0f; - - //LOG(INFO) << "Window: " << x0 << " => " << x1; - - result.reset(new OrthancStone::CairoSurface(frame_->GetWidth(), frame_->GetHeight(), false /* no alpha */)); - - const uint8_t* lut = NULL; - if (style.applyLut_) - { - if (Orthanc::EmbeddedResources::GetFileResourceSize(style.lut_) != 3 * 256) - { - // Invalid colormap - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - lut = reinterpret_cast<const uint8_t*>(Orthanc::EmbeddedResources::GetFileResourceBuffer(style.lut_)); - } - - Orthanc::ImageAccessor target; - result->GetWriteableAccessor(target); - - const unsigned int width = target.GetWidth(); - const unsigned int height = target.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - const float* p = reinterpret_cast<const float*>(frame_->GetConstRow(y)); - uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); - - for (unsigned int x = 0; x < width; x++, p++, q += 4) - { - uint8_t v = 0; - if (windowWidth >= 0.001f) // Avoid division by zero - { - if (*p >= x1) - { - v = 255; - } - else if (*p <= x0) - { - v = 0; - } - else - { - // https://en.wikipedia.org/wiki/Linear_interpolation - v = static_cast<uint8_t>(255.0f * (*p - x0) / (x1 - x0)); - } - - if (style.reverse_ ^ (photometric_ == Orthanc::PhotometricInterpretation_Monochrome1)) - { - v = 255 - v; - } - } - - if (style.applyLut_) - { - assert(lut != NULL); - q[3] = 255; - q[2] = lut[3 * v]; - q[1] = lut[3 * v + 1]; - q[0] = lut[3 * v + 2]; - } - else - { - q[3] = 255; - q[2] = v; - q[1] = v; - q[0] = v; - } - } - } - - return result.release(); - } - - - GrayscaleFrameRenderer::GrayscaleFrameRenderer(const Orthanc::ImageAccessor& frame, - const Deprecated::DicomFrameConverter& converter, - const OrthancStone::CoordinateSystem3D& framePlane, - double pixelSpacingX, - double pixelSpacingY, - bool isFullQuality) : - FrameRenderer(framePlane, pixelSpacingX, pixelSpacingY, isFullQuality), - frame_(Orthanc::Image::Clone(frame)), - defaultWindowCenter_(static_cast<float>(converter.GetDefaultWindowCenter())), - defaultWindowWidth_(static_cast<float>(converter.GetDefaultWindowWidth())), - photometric_(converter.GetPhotometricInterpretation()) - { - if (frame_.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - converter.ConvertFrameInplace(frame_); - assert(frame_.get() != NULL); - - if (frame_->GetFormat() != Orthanc::PixelFormat_Float32) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - } -}
--- a/Framework/Layers/GrayscaleFrameRenderer.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +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 "FrameRenderer.h" -#include "../Toolbox/DicomFrameConverter.h" - -namespace Deprecated -{ - class GrayscaleFrameRenderer : public FrameRenderer - { - private: - std::auto_ptr<Orthanc::ImageAccessor> frame_; // In Float32 - float defaultWindowCenter_; - float defaultWindowWidth_; - Orthanc::PhotometricInterpretation photometric_; - - protected: - virtual OrthancStone::CairoSurface* GenerateDisplay(const RenderStyle& style); - - public: - GrayscaleFrameRenderer(const Orthanc::ImageAccessor& frame, - const Deprecated::DicomFrameConverter& converter, - const OrthancStone::CoordinateSystem3D& framePlane, - double pixelSpacingX, - double pixelSpacingY, - bool isFullQuality); - }; -}
--- a/Framework/Layers/ILayerRenderer.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +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 "../Viewport/CairoContext.h" -#include "../Toolbox/CoordinateSystem3D.h" -#include "../Toolbox/ViewportGeometry.h" -#include "RenderStyle.h" - -namespace Deprecated -{ - class ILayerRenderer : public boost::noncopyable - { - public: - virtual ~ILayerRenderer() - { - } - - virtual bool RenderLayer(OrthancStone::CairoContext& context, - const ViewportGeometry& view) = 0; - - virtual void SetLayerStyle(const RenderStyle& style) = 0; - - virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane() = 0; - - virtual bool IsFullQuality() = 0; - }; -}
--- a/Framework/Layers/IVolumeSlicer.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +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 "ILayerRenderer.h" -#include "../Toolbox/Slice.h" -#include "../../Framework/Messages/IObservable.h" -#include "../../Framework/Messages/IMessage.h" -#include "Core/Images/Image.h" -#include <boost/shared_ptr.hpp> - -namespace Deprecated -{ - class IVolumeSlicer : public OrthancStone::IObservable - { - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeSlicer); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeSlicer); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeSlicer); - - class SliceContentChangedMessage : public OrthancStone::OriginMessage<IVolumeSlicer> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const Deprecated::Slice& slice_; - - public: - SliceContentChangedMessage(IVolumeSlicer& origin, - const Deprecated::Slice& slice) : - OriginMessage(origin), - slice_(slice) - { - } - - const Deprecated::Slice& GetSlice() const - { - return slice_; - } - }; - - - class LayerReadyMessage : public OrthancStone::OriginMessage<IVolumeSlicer> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - public: - class IRendererFactory : public boost::noncopyable - { - public: - virtual ~IRendererFactory() - { - } - - virtual ILayerRenderer* CreateRenderer() const = 0; - }; - - private: - const IRendererFactory& factory_; - const OrthancStone::CoordinateSystem3D& slice_; - - public: - LayerReadyMessage(IVolumeSlicer& origin, - const IRendererFactory& rendererFactory, - const OrthancStone::CoordinateSystem3D& slice) : - OriginMessage(origin), - factory_(rendererFactory), - slice_(slice) - { - } - - ILayerRenderer* CreateRenderer() const - { - return factory_.CreateRenderer(); - } - - const OrthancStone::CoordinateSystem3D& GetSlice() const - { - return slice_; - } - }; - - - class LayerErrorMessage : public OrthancStone::OriginMessage<IVolumeSlicer> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const OrthancStone::CoordinateSystem3D& slice_; - - public: - LayerErrorMessage(IVolumeSlicer& origin, - const OrthancStone::CoordinateSystem3D& slice) : - OriginMessage(origin), - slice_(slice) - { - } - - const OrthancStone::CoordinateSystem3D& GetSlice() const - { - return slice_; - } - }; - - - IVolumeSlicer(OrthancStone::MessageBroker& broker) : - IObservable(broker) - { - } - - virtual ~IVolumeSlicer() - { - } - - virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, - const OrthancStone::CoordinateSystem3D& viewportSlice) = 0; - - virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) = 0; - }; -}
--- a/Framework/Layers/LineLayerRenderer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +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 "LineLayerRenderer.h" - -namespace Deprecated -{ - LineLayerRenderer::LineLayerRenderer(double x1, - double y1, - double x2, - double y2, - const OrthancStone::CoordinateSystem3D& plane) : - x1_(x1), - y1_(y1), - x2_(x2), - y2_(y2), - plane_(plane) - { - RenderStyle style; - SetLayerStyle(style); - } - - - bool LineLayerRenderer::RenderLayer(OrthancStone::CairoContext& context, - const ViewportGeometry& view) - { - if (visible_) - { - context.SetSourceColor(color_); - - cairo_t *cr = context.GetObject(); - cairo_set_line_width(cr, 1.0 / view.GetZoom()); - cairo_move_to(cr, x1_, y1_); - cairo_line_to(cr, x2_, y2_); - cairo_stroke(cr); - } - - return true; - } - - - void LineLayerRenderer::SetLayerStyle(const RenderStyle& style) - { - visible_ = style.visible_; - color_[0] = style.drawColor_[0]; - color_[1] = style.drawColor_[1]; - color_[2] = style.drawColor_[2]; - } -}
--- a/Framework/Layers/LineLayerRenderer.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +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 "ILayerRenderer.h" - -namespace Deprecated -{ - class LineLayerRenderer : public ILayerRenderer - { - private: - double x1_; - double y1_; - double x2_; - double y2_; - OrthancStone::CoordinateSystem3D plane_; - bool visible_; - uint8_t color_[3]; - - public: - LineLayerRenderer(double x1, - double y1, - double x2, - double y2, - const OrthancStone::CoordinateSystem3D& plane); - - virtual bool RenderLayer(OrthancStone::CairoContext& context, - const ViewportGeometry& view); - - virtual void SetLayerStyle(const RenderStyle& style); - - virtual const OrthancStone::CoordinateSystem3D& GetLayerPlane() - { - return plane_; - } - - virtual bool IsFullQuality() - { - return true; - } - }; -}
--- a/Framework/Layers/LineMeasureTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +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 "LineMeasureTracker.h" - -#include <stdio.h> - -namespace Deprecated -{ - LineMeasureTracker::LineMeasureTracker(IStatusBar* statusBar, - const OrthancStone::CoordinateSystem3D& slice, - double x, - double y, - uint8_t red, - uint8_t green, - uint8_t blue, - const Orthanc::Font& font) : - statusBar_(statusBar), - slice_(slice), - x1_(x), - y1_(y), - x2_(x), - y2_(y), - font_(font) - { - color_[0] = red; - color_[1] = green; - color_[2] = blue; - } - - - void LineMeasureTracker::Render(OrthancStone::CairoContext& context, - double zoom) - { - context.SetSourceColor(color_[0], color_[1], color_[2]); - - cairo_t* cr = context.GetObject(); - cairo_set_line_width(cr, 2.0 / zoom); - cairo_move_to(cr, x1_, y1_); - cairo_line_to(cr, x2_, y2_); - cairo_stroke(cr); - - if (y2_ - y1_ < 0) - { - context.DrawText(font_, FormatLength(), x2_, y2_ - 5, OrthancStone::BitmapAnchor_BottomCenter); - } - else - { - context.DrawText(font_, FormatLength(), x2_, y2_ + 5, OrthancStone::BitmapAnchor_TopCenter); - } - } - - - double LineMeasureTracker::GetLength() const // In millimeters - { - OrthancStone::Vector a = slice_.MapSliceToWorldCoordinates(x1_, y1_); - OrthancStone::Vector b = slice_.MapSliceToWorldCoordinates(x2_, y2_); - return boost::numeric::ublas::norm_2(b - a); - } - - - std::string LineMeasureTracker::FormatLength() const - { - char buf[64]; - sprintf(buf, "%0.01f cm", GetLength() / 10.0); - return buf; - } - - void LineMeasureTracker::MouseMove(int displayX, - int displayY, - double x, - double y, - const std::vector<Touch>& displayTouches, - const std::vector<Touch>& sceneTouches) - { - x2_ = x; - y2_ = y; - - if (statusBar_ != NULL) - { - statusBar_->SetMessage("Line length: " + FormatLength()); - } - } -}
--- a/Framework/Layers/LineMeasureTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +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 "../Widgets/IWorldSceneMouseTracker.h" - -#include "../Viewport/IStatusBar.h" -#include "../Toolbox/CoordinateSystem3D.h" - -namespace Deprecated -{ - class LineMeasureTracker : public IWorldSceneMouseTracker - { - private: - IStatusBar* statusBar_; - OrthancStone::CoordinateSystem3D slice_; - double x1_; - double y1_; - double x2_; - double y2_; - uint8_t color_[3]; - unsigned int fontSize_; - const Orthanc::Font& font_; - - public: - LineMeasureTracker(IStatusBar* statusBar, - const OrthancStone::CoordinateSystem3D& slice, - double x, - double y, - uint8_t red, - uint8_t green, - uint8_t blue, - const Orthanc::Font& font); - - virtual bool HasRender() const - { - return true; - } - - virtual void Render(OrthancStone::CairoContext& context, - double zoom); - - double GetLength() const; // In millimeters - - std::string FormatLength() const; - - virtual void MouseUp() - { - // Possibly create a new landmark "volume" with the line in subclasses - } - - virtual void MouseMove(int displayX, - int displayY, - double x, - double y, - const std::vector<Touch>& displayTouches, - const std::vector<Touch>& sceneTouches); - }; -}
--- a/Framework/Layers/RenderStyle.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +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 "RenderStyle.h" - -#include <Core/OrthancException.h> - -namespace Deprecated -{ - RenderStyle::RenderStyle() - { - visible_ = true; - reverse_ = false; - windowing_ = OrthancStone::ImageWindowing_Custom; - alpha_ = 1; - applyLut_ = false; - lut_ = Orthanc::EmbeddedResources::COLORMAP_HOT; - drawGrid_ = false; - drawColor_[0] = 255; - drawColor_[1] = 255; - drawColor_[2] = 255; - customWindowCenter_ = 128; - customWindowWidth_ = 256; - interpolation_ = OrthancStone::ImageInterpolation_Nearest; - fontSize_ = 14; - } - - - void RenderStyle::ComputeWindowing(float& targetCenter, - float& targetWidth, - float defaultCenter, - float defaultWidth) const - { - if (windowing_ == OrthancStone::ImageWindowing_Custom) - { - targetCenter = customWindowCenter_; - targetWidth = customWindowWidth_; - } - else - { - return ::OrthancStone::ComputeWindowing - (targetCenter, targetWidth, windowing_, defaultCenter, defaultWidth); - } - } - - - void RenderStyle::SetColor(uint8_t red, - uint8_t green, - uint8_t blue) - { - drawColor_[0] = red; - drawColor_[1] = green; - drawColor_[2] = blue; - } -}
--- a/Framework/Layers/RenderStyle.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +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 "../StoneEnumerations.h" - -#include <EmbeddedResources.h> - -#include <stdint.h> - -namespace Deprecated -{ - struct RenderStyle - { - bool visible_; - bool reverse_; - OrthancStone::ImageWindowing windowing_; - float alpha_; // In [0,1] - bool applyLut_; - Orthanc::EmbeddedResources::FileResourceId lut_; - bool drawGrid_; - uint8_t drawColor_[3]; - float customWindowCenter_; - float customWindowWidth_; - OrthancStone::ImageInterpolation interpolation_; - unsigned int fontSize_; - - RenderStyle(); - - void ComputeWindowing(float& targetCenter, - float& targetWidth, - float defaultCenter, - float defaultWidth) const; - - void SetColor(uint8_t red, - uint8_t green, - uint8_t blue); - }; -}
--- a/Framework/Layers/SeriesFrameRendererFactory.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,177 +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 "SeriesFrameRendererFactory.h" - -#include "FrameRenderer.h" - -#include <OrthancException.h> -#include <Logging.h> -#include <Toolbox.h> -#include <Plugins/Samples/Common/OrthancPluginException.h> -#include <Plugins/Samples/Common/DicomDatasetReader.h> - - -namespace Deprecated -{ - void SeriesFrameRendererFactory::ReadCurrentFrameDataset(size_t frame) - { - if (currentDataset_.get() != NULL && - (fast_ || currentFrame_ == frame)) - { - // The frame has not changed since the previous call, no need to - // update the DICOM dataset - return; - } - - currentDataset_.reset(loader_->DownloadDicom(frame)); - currentFrame_ = frame; - - if (currentDataset_.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - void SeriesFrameRendererFactory::GetCurrentPixelSpacing(double& spacingX, - double& spacingY) const - { - if (currentDataset_.get() == NULL) - { - // There was no previous call "ReadCurrentFrameDataset()" - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - GeometryToolbox::GetPixelSpacing(spacingX, spacingY, *currentDataset_); - } - - - double SeriesFrameRendererFactory::GetCurrentSliceThickness() const - { - if (currentDataset_.get() == NULL) - { - // There was no previous call "ReadCurrentFrameDataset()" - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - try - { - OrthancPlugins::DicomDatasetReader reader(*currentDataset_); - - double thickness; - if (reader.GetDoubleValue(thickness, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS)) - { - return thickness; - } - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - } - - // Some arbitrary large slice thickness - return std::numeric_limits<double>::infinity(); - } - - - SeriesFrameRendererFactory::SeriesFrameRendererFactory(ISeriesLoader* loader, // Takes ownership - bool fast) : - loader_(loader), - currentFrame_(0), - fast_(fast) - { - if (loader == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - bool SeriesFrameRendererFactory::GetExtent(double& x1, - double& y1, - double& x2, - double& y2, - const SliceGeometry& viewportSlice) - { - if (currentDataset_.get() == NULL) - { - // There has been no previous call to - // "CreateLayerRenderer". Read some arbitrary DICOM frame, the - // one at the middle of the series. - unsigned int depth = loader_->GetGeometry().GetSliceCount(); - ReadCurrentFrameDataset(depth / 2); - } - - double spacingX, spacingY; - GetCurrentPixelSpacing(spacingX, spacingY); - - return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2, - viewportSlice, - loader_->GetGeometry().GetSlice(0), - loader_->GetWidth(), - loader_->GetHeight(), - spacingX, spacingY); - } - - - ILayerRenderer* SeriesFrameRendererFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice) - { - size_t closest; - double distance; - - bool isOpposite; - if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, loader_->GetGeometry().GetNormal(), viewportSlice.GetNormal()) || - !loader_->GetGeometry().ComputeClosestSlice(closest, distance, viewportSlice.GetOrigin())) - { - // Unable to compute the slice in the series that is the - // closest to the slice displayed by the viewport - return NULL; - } - - ReadCurrentFrameDataset(closest); - assert(currentDataset_.get() != NULL); - - double spacingX, spacingY; - GetCurrentPixelSpacing(spacingX, spacingY); - - if (distance <= GetCurrentSliceThickness() / 2.0) - { - SliceGeometry frameSlice(*currentDataset_); - return FrameRenderer::CreateRenderer(loader_->DownloadFrame(closest), - frameSlice, - *currentDataset_, - spacingX, spacingY, - true); - } - else - { - // The closest slice of the series is too far away from the - // slice displayed by the viewport - return NULL; - } - } - - - ISliceableVolume& SeriesFrameRendererFactory::GetSourceVolume() const - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } -}
--- a/Framework/Layers/SeriesFrameRendererFactory.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +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 "ILayerRendererFactory.h" - -#include "../Toolbox/ISeriesLoader.h" - -namespace Deprecated -{ - class SeriesFrameRendererFactory : public ILayerRendererFactory - { - private: - std::auto_ptr<ISeriesLoader> loader_; - size_t currentFrame_; - bool fast_; - - std::auto_ptr<OrthancPlugins::IDicomDataset> currentDataset_; - - void ReadCurrentFrameDataset(size_t frame); - - void GetCurrentPixelSpacing(double& spacingX, - double& spacingY) const; - - double GetCurrentSliceThickness() const; - - public: - SeriesFrameRendererFactory(ISeriesLoader* loader, // Takes ownership - bool fast); - - virtual bool GetExtent(double& x1, - double& y1, - double& x2, - double& y2, - const SliceGeometry& viewportSlice); - - virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice); - - virtual bool HasSourceVolume() const - { - return false; - } - - virtual ISliceableVolume& GetSourceVolume() const; - }; -}
--- a/Framework/Layers/SingleFrameRendererFactory.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +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 "SingleFrameRendererFactory.h" - -#include "FrameRenderer.h" -#include "../Toolbox/MessagingToolbox.h" -#include "../Toolbox/DicomFrameConverter.h" - -#include <OrthancException.h> -#include <Plugins/Samples/Common/FullOrthancDataset.h> -#include <Plugins/Samples/Common/DicomDatasetReader.h> - -namespace Deprecated -{ - SingleFrameRendererFactory::SingleFrameRendererFactory(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instanceId, - unsigned int frame) : - orthanc_(orthanc), - instance_(instanceId), - frame_(frame) - { - dicom_.reset(new OrthancPlugins::FullOrthancDataset(orthanc, "/instances/" + instanceId + "/tags")); - - DicomFrameConverter converter; - converter.ReadParameters(*dicom_); - format_ = converter.GetExpectedPixelFormat(); - } - - - bool SingleFrameRendererFactory::GetExtent(double& x1, - double& y1, - double& x2, - double& y2, - const SliceGeometry& viewportSlice) - { - // Assume that PixelSpacingX == PixelSpacingY == 1 - - OrthancPlugins::DicomDatasetReader reader(*dicom_); - - unsigned int width, height; - - if (!reader.GetUnsignedIntegerValue(width, OrthancPlugins::DICOM_TAG_COLUMNS) || - !reader.GetUnsignedIntegerValue(height, OrthancPlugins::DICOM_TAG_ROWS)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - x1 = 0; - y1 = 0; - x2 = static_cast<double>(width); - y2 = static_cast<double>(height); - - return true; - } - - - ILayerRenderer* SingleFrameRendererFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice) - { - SliceGeometry frameSlice(*dicom_); - return FrameRenderer::CreateRenderer(MessagingToolbox::DecodeFrame(orthanc_, instance_, frame_, format_), - frameSlice, *dicom_, 1, 1, true); - } - - - ISliceableVolume& SingleFrameRendererFactory::GetSourceVolume() const - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } -}
--- a/Framework/Layers/SingleFrameRendererFactory.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +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 "ILayerRendererFactory.h" -#include <Plugins/Samples/Common/IOrthancConnection.h> - -namespace Deprecated -{ - class SingleFrameRendererFactory : public ILayerRendererFactory - { - private: - OrthancPlugins::IOrthancConnection& orthanc_; - std::auto_ptr<OrthancPlugins::IDicomDataset> dicom_; - - std::string instance_; - unsigned int frame_; - Orthanc::PixelFormat format_; - - public: - SingleFrameRendererFactory(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instanceId, - unsigned int frame); - - const OrthancPlugins::IDicomDataset& GetDataset() const - { - return *dicom_; - } - - SliceGeometry GetSliceGeometry() - { - return SliceGeometry(*dicom_); - } - - virtual bool GetExtent(double& x1, - double& y1, - double& x2, - double& y2, - const SliceGeometry& viewportSlice); - - virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice); - - virtual bool HasSourceVolume() const - { - return false; - } - - virtual ISliceableVolume& GetSourceVolume() const; - }; -}
--- a/Framework/Layers/SliceOutlineRenderer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +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 "SliceOutlineRenderer.h" - -namespace Deprecated -{ - bool SliceOutlineRenderer::RenderLayer(OrthancStone::CairoContext& context, - const ViewportGeometry& view) - { - if (style_.visible_) - { - cairo_t *cr = context.GetObject(); - cairo_save(cr); - - context.SetSourceColor(style_.drawColor_); - - double x1 = -0.5 * pixelSpacingX_; - double y1 = -0.5 * pixelSpacingY_; - - cairo_set_line_width(cr, 1.0 / view.GetZoom()); - cairo_rectangle(cr, x1, y1, - static_cast<double>(width_) * pixelSpacingX_, - static_cast<double>(height_) * pixelSpacingY_); - - double handleSize = 10.0f / view.GetZoom(); - cairo_move_to(cr, x1 + handleSize, y1); - cairo_line_to(cr, x1, y1 + handleSize); - - cairo_stroke(cr); - cairo_restore(cr); - } - - return true; - } -}
--- a/Framework/Layers/SliceOutlineRenderer.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +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 "ILayerRenderer.h" -#include "../Toolbox/Slice.h" - -namespace Deprecated -{ - class SliceOutlineRenderer : public ILayerRenderer - { - private: - OrthancStone::CoordinateSystem3D geometry_; - double pixelSpacingX_; - double pixelSpacingY_; - unsigned int width_; - unsigned int height_; - RenderStyle style_; - - public: - SliceOutlineRenderer(const Slice& slice) : - geometry_(slice.GetGeometry()), - pixelSpacingX_(slice.GetPixelSpacingX()), - pixelSpacingY_(slice.GetPixelSpacingY()), - width_(slice.GetWidth()), - height_(slice.GetHeight()) - { - } - - virtual bool RenderLayer(OrthancStone::CairoContext& context, - const ViewportGeometry& view); - - virtual void SetLayerStyle(const RenderStyle& style) - { - style_ = style; - } - - virtual const OrthancStone::CoordinateSystem3D& GetLayerSlice() - { - return geometry_; - } - - virtual bool IsFullQuality() - { - return true; - } - }; -}
--- a/Framework/Loaders/BasicFetchingItemsSorter.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Loaders/BasicFetchingItemsSorter.h Mon Jun 24 14:35:00 2019 +0200 @@ -31,6 +31,15 @@ unsigned int itemsCount_; public: + class Factory : public IFactory + { + public: + virtual IFetchingItemsSorter* CreateSorter(unsigned int itemsCount) const + { + return new BasicFetchingItemsSorter(itemsCount); + } + }; + BasicFetchingItemsSorter(unsigned int itemsCount); virtual unsigned int GetItemsCount() const
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/DicomStructureSetLoader.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,264 @@ +/** + * 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 "DicomStructureSetLoader.h" + +#include "../Scene2D/PolylineSceneLayer.h" +#include "../Toolbox/GeometryToolbox.h" + +namespace OrthancStone +{ + class DicomStructureSetLoader::AddReferencedInstance : public LoaderStateMachine::State + { + private: + std::string instanceId_; + + public: + AddReferencedInstance(DicomStructureSetLoader& that, + const std::string& instanceId) : + State(that), + instanceId_(instanceId) + { + } + + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + Json::Value tags; + message.ParseJsonBody(tags); + + Orthanc::DicomMap dicom; + dicom.FromDicomAsJson(tags); + + DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); + loader.content_->AddReferencedSlice(dicom); + + loader.countProcessedInstances_ ++; + assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_); + + if (loader.countProcessedInstances_ == loader.countReferencedInstances_) + { + // All the referenced instances have been loaded, finalize the RT-STRUCT + loader.content_->CheckReferencedSlices(); + loader.revision_++; + } + } + }; + + + // State that converts a "SOP Instance UID" to an Orthanc identifier + class DicomStructureSetLoader::LookupInstance : public LoaderStateMachine::State + { + private: + std::string sopInstanceUid_; + + public: + LookupInstance(DicomStructureSetLoader& that, + const std::string& sopInstanceUid) : + State(that), + sopInstanceUid_(sopInstanceUid) + { + } + + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); + + Json::Value lookup; + message.ParseJsonBody(lookup); + + if (lookup.type() != Json::arrayValue || + lookup.size() != 1 || + !lookup[0].isMember("Type") || + !lookup[0].isMember("Path") || + lookup[0]["Type"].type() != Json::stringValue || + lookup[0]["ID"].type() != Json::stringValue || + lookup[0]["Type"].asString() != "Instance") + { + std::stringstream msg; + msg << "Unknown resource! message.GetAnswer() = " << message.GetAnswer() << " message.GetAnswerHeaders() = "; + for (OrthancRestApiCommand::HttpHeaders::const_iterator it = message.GetAnswerHeaders().begin(); + it != message.GetAnswerHeaders().end(); ++it) + { + msg << "\nkey: \"" << it->first << "\" value: \"" << it->second << "\"\n"; + } + const std::string msgStr = msg.str(); + LOG(ERROR) << msgStr; + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + const std::string instanceId = lookup[0]["ID"].asString(); + + { + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetUri("/instances/" + instanceId + "/tags"); + command->SetPayload(new AddReferencedInstance(loader, instanceId)); + Schedule(command.release()); + } + } + }; + + + class DicomStructureSetLoader::LoadStructure : public LoaderStateMachine::State + { + public: + LoadStructure(DicomStructureSetLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>(); + + { + OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer()); + loader.content_.reset(new DicomStructureSet(dicom)); + } + + std::set<std::string> instances; + loader.content_->GetReferencedInstances(instances); + + loader.countReferencedInstances_ = static_cast<unsigned int>(instances.size()); + + for (std::set<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetUri("/tools/lookup"); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetBody(*it); + command->SetPayload(new LookupInstance(loader, *it)); + //LOG(TRACE) << "About to schedule a /tools/lookup POST request. URI = " << command->GetUri() << " Body size = " << (*it).size() << " Body = " << (*it) << "\n"; + Schedule(command.release()); + } + } + }; + + + class DicomStructureSetLoader::Slice : public IExtractedSlice + { + private: + const DicomStructureSet& content_; + uint64_t revision_; + bool isValid_; + + public: + Slice(const DicomStructureSet& content, + uint64_t revision, + const CoordinateSystem3D& cuttingPlane) : + content_(content), + revision_(revision) + { + bool opposite; + + const Vector normal = content.GetNormal(); + isValid_ = ( + GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) || + GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) || + GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY())); + } + + virtual bool IsValid() + { + return isValid_; + } + + virtual uint64_t GetRevision() + { + return revision_; + } + + virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane) + { + assert(isValid_); + + std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer); + layer->SetThickness(2); + + for (size_t i = 0; i < content_.GetStructuresCount(); i++) + { + const Color& color = content_.GetStructureColor(i); + + std::vector< std::vector<DicomStructureSet::PolygonPoint> > polygons; + + if (content_.ProjectStructure(polygons, i, cuttingPlane)) + { + for (size_t j = 0; j < polygons.size(); j++) + { + PolylineSceneLayer::Chain chain; + chain.resize(polygons[j].size()); + + for (size_t k = 0; k < polygons[j].size(); k++) + { + chain[k] = ScenePoint2D(polygons[j][k].first, polygons[j][k].second); + } + + layer->AddChain(chain, true /* closed */, color); + } + } + } + + return layer.release(); + } + }; + + + DicomStructureSetLoader::DicomStructureSetLoader(IOracle& oracle, + IObservable& oracleObservable) : + LoaderStateMachine(oracle, oracleObservable), + revision_(0), + countProcessedInstances_(0), + countReferencedInstances_(0) + { + } + + + void DicomStructureSetLoader::LoadInstance(const std::string& instanceId) + { + Start(); + + instanceId_ = instanceId; + + { + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3006-0050"); + command->SetPayload(new LoadStructure(*this)); + Schedule(command.release()); + } + } + + + IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane) + { + if (content_.get() == NULL) + { + // Geometry is not available yet + return new IVolumeSlicer::InvalidSlice; + } + else + { + return new Slice(*content_, revision_, cuttingPlane); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/DicomStructureSetLoader.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,56 @@ +/** + * 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/DicomStructureSet.h" +#include "../Volumes/IVolumeSlicer.h" +#include "LoaderStateMachine.h" + +namespace OrthancStone +{ + class DicomStructureSetLoader : + public LoaderStateMachine, + public IVolumeSlicer + { + private: + class Slice; + + // States of LoaderStateMachine + class AddReferencedInstance; // 3rd state + class LookupInstance; // 2nd state + class LoadStructure; // 1st state + + std::auto_ptr<DicomStructureSet> content_; + uint64_t revision_; + std::string instanceId_; + unsigned int countProcessedInstances_; + unsigned int countReferencedInstances_; + + public: + DicomStructureSetLoader(IOracle& oracle, + IObservable& oracleObservable); + + void LoadInstance(const std::string& instanceId); + + virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane); + }; +}
--- a/Framework/Loaders/IFetchingItemsSorter.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Loaders/IFetchingItemsSorter.h Mon Jun 24 14:35:00 2019 +0200 @@ -29,6 +29,16 @@ class IFetchingItemsSorter : public boost::noncopyable { public: + class IFactory : public boost::noncopyable + { + public: + virtual ~IFactory() + { + } + + virtual IFetchingItemsSorter* CreateSorter(unsigned int itemsCount) const = 0; + }; + virtual ~IFetchingItemsSorter() { }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/LoaderStateMachine.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,177 @@ +/** + * 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 "LoaderStateMachine.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + void LoaderStateMachine::State::Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + void LoaderStateMachine::State::Handle(const GetOrthancImageCommand::SuccessMessage& message) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + void LoaderStateMachine::State::Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + void LoaderStateMachine::Schedule(OracleCommandWithPayload* command) + { + std::auto_ptr<OracleCommandWithPayload> protection(command); + + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (!command->HasPayload()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "The payload must contain the next state"); + } + + pendingCommands_.push_back(protection.release()); + Step(); + } + + + void LoaderStateMachine::Start() + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + active_ = true; + + for (size_t i = 0; i < simultaneousDownloads_; i++) + { + Step(); + } + } + + + void LoaderStateMachine::Step() + { + if (!pendingCommands_.empty() && + activeCommands_ < simultaneousDownloads_) + { + oracle_.Schedule(*this, pendingCommands_.front()); + pendingCommands_.pop_front(); + + activeCommands_++; + } + } + + + void LoaderStateMachine::Clear() + { + for (PendingCommands::iterator it = pendingCommands_.begin(); + it != pendingCommands_.end(); ++it) + { + delete *it; + } + + pendingCommands_.clear(); + } + + + void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message) + { + LOG(ERROR) << "Error in the state machine, stopping all processing"; + LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " << + message.GetException().GetDetails(); + Clear(); + } + + + template <typename T> + void LoaderStateMachine::HandleSuccessMessage(const T& message) + { + assert(activeCommands_ > 0); + activeCommands_--; + + try + { + dynamic_cast<State&>(message.GetOrigin().GetPayload()).Handle(message); + Step(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Error in the state machine, stopping all processing: " << + e.What() << " Details: " << e.GetDetails(); + Clear(); + } + } + + + LoaderStateMachine::LoaderStateMachine(IOracle& oracle, + IObservable& oracleObservable) : + IObserver(oracleObservable.GetBroker()), + oracle_(oracle), + active_(false), + simultaneousDownloads_(4), + activeCommands_(0) + { + oracleObservable.RegisterObserverCallback( + new Callable<LoaderStateMachine, OrthancRestApiCommand::SuccessMessage> + (*this, &LoaderStateMachine::HandleSuccessMessage)); + + oracleObservable.RegisterObserverCallback( + new Callable<LoaderStateMachine, GetOrthancImageCommand::SuccessMessage> + (*this, &LoaderStateMachine::HandleSuccessMessage)); + + oracleObservable.RegisterObserverCallback( + new Callable<LoaderStateMachine, GetOrthancWebViewerJpegCommand::SuccessMessage> + (*this, &LoaderStateMachine::HandleSuccessMessage)); + + oracleObservable.RegisterObserverCallback( + new Callable<LoaderStateMachine, OracleCommandExceptionMessage> + (*this, &LoaderStateMachine::HandleExceptionMessage)); + } + + + void LoaderStateMachine::SetSimultaneousDownloads(unsigned int count) + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else if (count == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + simultaneousDownloads_ = count; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/LoaderStateMachine.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,119 @@ +/** + * 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 "../Messages/IObservable.h" +#include "../Messages/IObserver.h" +#include "../Oracle/GetOrthancImageCommand.h" +#include "../Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../Oracle/IOracle.h" +#include "../Oracle/OracleCommandExceptionMessage.h" +#include "../Oracle/OrthancRestApiCommand.h" + +#include <Core/IDynamicObject.h> + +#include <list> + +namespace OrthancStone +{ + /** + This class is supplied with Oracle commands and will schedule up to + simultaneousDownloads_ of them at the same time, then will schedule the + rest once slots become available. It is used, a.o., by the + OrtancMultiframeVolumeLoader class. + */ + class LoaderStateMachine : public IObserver + { + protected: + class State : public Orthanc::IDynamicObject + { + private: + LoaderStateMachine& that_; + + public: + State(LoaderStateMachine& that) : + that_(that) + { + } + + State(const State& currentState) : + that_(currentState.that_) + { + } + + void Schedule(OracleCommandWithPayload* command) const + { + that_.Schedule(command); + } + + template <typename T> + T& GetLoader() const + { + return dynamic_cast<T&>(that_); + } + + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message); + + virtual void Handle(const GetOrthancImageCommand::SuccessMessage& message); + + virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message); + }; + + void Schedule(OracleCommandWithPayload* command); + + void Start(); + + private: + void Step(); + + void Clear(); + + void HandleExceptionMessage(const OracleCommandExceptionMessage& message); + + template <typename T> + void HandleSuccessMessage(const T& message); + + typedef std::list<IOracleCommand*> PendingCommands; + + IOracle& oracle_; + bool active_; + unsigned int simultaneousDownloads_; + PendingCommands pendingCommands_; + unsigned int activeCommands_; + + public: + LoaderStateMachine(IOracle& oracle, + IObservable& oracleObservable); + + virtual ~LoaderStateMachine() + { + Clear(); + } + + bool IsActive() const + { + return active_; + } + + void SetSimultaneousDownloads(unsigned int count); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,356 @@ +/** + * 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 "OrthancMultiframeVolumeLoader.h" + +#include <Core/Endianness.h> +#include <Core/Toolbox.h> + +namespace OrthancStone +{ + class OrthancMultiframeVolumeLoader::LoadRTDoseGeometry : public LoaderStateMachine::State + { + private: + std::auto_ptr<Orthanc::DicomMap> dicom_; + + public: + LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that, + Orthanc::DicomMap* dicom) : + State(that), + dicom_(dicom) + { + if (dicom == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + } + + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + // Complete the DICOM tags with just-received "Grid Frame Offset Vector" + std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer()); + dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false); + + GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_); + } + }; + + + static std::string GetSopClassUid(const Orthanc::DicomMap& dicom) + { + std::string s; + if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "DICOM file without SOP class UID"); + } + else + { + return s; + } + } + + + class OrthancMultiframeVolumeLoader::LoadGeometry : public State + { + public: + LoadGeometry(OrthancMultiframeVolumeLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>(); + + Json::Value body; + message.ParseJsonBody(body); + + if (body.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap); + dicom->FromDicomAsJson(body); + + if (StringToSopClassUid(GetSopClassUid(*dicom)) == SopClassUid_RTDose) + { + // Download the "Grid Frame Offset Vector" DICOM tag, that is + // mandatory for RT-DOSE, but is too long to be returned by default + + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" + + Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format()); + command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release())); + + Schedule(command.release()); + } + else + { + loader.SetGeometry(*dicom); + } + } + }; + + + + class OrthancMultiframeVolumeLoader::LoadTransferSyntax : public State + { + public: + LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer()); + } + }; + + + class OrthancMultiframeVolumeLoader::LoadUncompressedPixelData : public State + { + public: + LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) : + State(that) + { + } + + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer()); + } + }; + + + const std::string& OrthancMultiframeVolumeLoader::GetInstanceId() const + { + if (IsActive()) + { + return instanceId_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void OrthancMultiframeVolumeLoader::ScheduleFrameDownloads() + { + if (transferSyntaxUid_.empty() || + !volume_->HasGeometry()) + { + return; + } + /* + 1.2.840.10008.1.2 Implicit VR Endian: Default Transfer Syntax for DICOM + 1.2.840.10008.1.2.1 Explicit VR Little Endian + 1.2.840.10008.1.2.2 Explicit VR Big Endian + + See https://www.dicomlibrary.com/dicom/transfer-syntax/ + */ + if (transferSyntaxUid_ == "1.2.840.10008.1.2" || + transferSyntaxUid_ == "1.2.840.10008.1.2.1" || + transferSyntaxUid_ == "1.2.840.10008.1.2.2") + { + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetUri("/instances/" + instanceId_ + "/content/" + + Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0"); + command->SetPayload(new LoadUncompressedPixelData(*this)); + Schedule(command.release()); + } + else + { + throw Orthanc::OrthancException( + Orthanc::ErrorCode_NotImplemented, + "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_); + } + } + + + void OrthancMultiframeVolumeLoader::SetTransferSyntax(const std::string& transferSyntax) + { + transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax); + ScheduleFrameDownloads(); + } + + + void OrthancMultiframeVolumeLoader::SetGeometry(const Orthanc::DicomMap& dicom) + { + DicomInstanceParameters parameters(dicom); + volume_->SetDicomParameters(parameters); + + Orthanc::PixelFormat format; + if (!parameters.GetImageInformation().ExtractPixelFormat(format, true)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + double spacingZ; + switch (parameters.GetSopClassUid()) + { + case SopClassUid_RTDose: + spacingZ = parameters.GetThickness(); + break; + + default: + throw Orthanc::OrthancException( + Orthanc::ErrorCode_NotImplemented, + "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom)); + } + + const unsigned int width = parameters.GetImageInformation().GetWidth(); + const unsigned int height = parameters.GetImageInformation().GetHeight(); + const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames(); + + { + VolumeImageGeometry geometry; + geometry.SetSize(width, height, depth); + geometry.SetAxialGeometry(parameters.GetGeometry()); + geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(), + parameters.GetPixelSpacingY(), spacingZ); + volume_->Initialize(geometry, format); + } + + volume_->GetPixelData().Clear(); + + ScheduleFrameDownloads(); + + BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_)); + } + + + ORTHANC_FORCE_INLINE + static void CopyPixel(uint32_t& target, + const void* source) + { + // TODO - check alignement? + target = le32toh(*reinterpret_cast<const uint32_t*>(source)); + } + + + template <typename T> + void OrthancMultiframeVolumeLoader::CopyPixelData(const std::string& pixelData) + { + ImageBuffer3D& target = volume_->GetPixelData(); + + const unsigned int bpp = target.GetBytesPerPixel(); + const unsigned int width = target.GetWidth(); + const unsigned int height = target.GetHeight(); + const unsigned int depth = target.GetDepth(); + + if (pixelData.size() != bpp * width * height * depth) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "The pixel data has not the proper size"); + } + + if (pixelData.empty()) + { + return; + } + + const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str()); + + for (unsigned int z = 0; z < depth; z++) + { + ImageBuffer3D::SliceWriter writer(target, VolumeProjection_Axial, z); + + assert (writer.GetAccessor().GetWidth() == width && + writer.GetAccessor().GetHeight() == height); + + for (unsigned int y = 0; y < height; y++) + { + assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat())); + + T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + CopyPixel(*target, source); + + target ++; + source += bpp; + } + } + } + } + + + void OrthancMultiframeVolumeLoader::SetUncompressedPixelData(const std::string& pixelData) + { + switch (volume_->GetPixelData().GetFormat()) + { + case Orthanc::PixelFormat_Grayscale32: + CopyPixelData<uint32_t>(pixelData); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + volume_->IncrementRevision(); + + BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_)); + } + + + OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume, + IOracle& oracle, + IObservable& oracleObservable) : + LoaderStateMachine(oracle, oracleObservable), + IObservable(oracleObservable.GetBroker()), + volume_(volume) + { + if (volume.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + + void OrthancMultiframeVolumeLoader::LoadInstance(const std::string& instanceId) + { + Start(); + + instanceId_ = instanceId; + + { + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetUri("/instances/" + instanceId + "/tags"); + command->SetPayload(new LoadGeometry(*this)); + Schedule(command.release()); + } + + { + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax"); + command->SetPayload(new LoadTransferSyntax(*this)); + Schedule(command.release()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,66 @@ +/** + * 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 "LoaderStateMachine.h" +#include "../Volumes/DicomVolumeImage.h" + +#include <boost/shared_ptr.hpp> + +namespace OrthancStone +{ + class OrthancMultiframeVolumeLoader : + public LoaderStateMachine, + public IObservable + { + private: + class LoadRTDoseGeometry; + class LoadGeometry; + class LoadTransferSyntax; + class LoadUncompressedPixelData; + + boost::shared_ptr<DicomVolumeImage> volume_; + std::string instanceId_; + std::string transferSyntaxUid_; + + + const std::string& GetInstanceId() const; + + void ScheduleFrameDownloads(); + + void SetTransferSyntax(const std::string& transferSyntax); + + void SetGeometry(const Orthanc::DicomMap& dicom); + + template <typename T> + void CopyPixelData(const std::string& pixelData); + + void SetUncompressedPixelData(const std::string& pixelData); + + public: + OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume, + IOracle& oracle, + IObservable& oracleObservable); + + void LoadInstance(const std::string& instanceId); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,476 @@ +/** + * 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 "OrthancSeriesVolumeProgressiveLoader.h" + +#include "../Toolbox/GeometryToolbox.h" +#include "../Volumes/DicomVolumeImageMPRSlicer.h" +#include "BasicFetchingItemsSorter.h" +#include "BasicFetchingStrategy.h" + +#include <Core/Images/ImageProcessing.h> +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + class OrthancSeriesVolumeProgressiveLoader::ExtractedSlice : public DicomVolumeImageMPRSlicer::Slice + { + private: + const OrthancSeriesVolumeProgressiveLoader& that_; + + public: + ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that, + const CoordinateSystem3D& plane) : + DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane), + that_(that) + { + if (IsValid()) + { + if (GetProjection() == VolumeProjection_Axial) + { + // For coronal and sagittal projections, we take the global + // revision of the volume because even if a single slice changes, + // this means the projection will yield a different result --> + // we must increase the revision as soon as any slice changes + SetRevision(that_.seriesGeometry_.GetSliceRevision(GetSliceIndex())); + } + + if (that_.strategy_.get() != NULL && + GetProjection() == VolumeProjection_Axial) + { + that_.strategy_->SetCurrent(GetSliceIndex()); + } + } + } + }; + + + + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSlice(size_t index, + const DicomInstanceParameters& reference) const + { + const DicomInstanceParameters& slice = *slices_[index]; + + if (!GeometryToolbox::IsParallel( + reference.GetGeometry().GetNormal(), + slice.GetGeometry().GetNormal())) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, + "A slice in the volume image is not parallel to the others"); + } + + if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, + "The pixel format changes across the slices of the volume image"); + } + + if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() || + reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize, + "The width/height of slices are not constant in the volume image"); + } + + if (!LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) || + !LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY())) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, + "The pixel spacing of the slices change across the volume image"); + } + } + + + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckVolume() const + { + for (size_t i = 0; i < slices_.size(); i++) + { + assert(slices_[i] != NULL); + if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, + "This class does not support multi-frame images"); + } + } + + if (slices_.size() != 0) + { + const DicomInstanceParameters& reference = *slices_[0]; + + for (size_t i = 1; i < slices_.size(); i++) + { + CheckSlice(i, reference); + } + } + } + + + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::Clear() + { + for (size_t i = 0; i < slices_.size(); i++) + { + assert(slices_[i] != NULL); + delete slices_[i]; + } + + slices_.clear(); + slicesRevision_.clear(); + } + + + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSliceIndex(size_t index) const + { + if (!HasGeometry()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else if (index >= slices_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + assert(slices_.size() == GetImageGeometry().GetDepth() && + slices_.size() == slicesRevision_.size()); + } + } + + + // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" + // (called with the slices created in LoadGeometry) + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::ComputeGeometry(SlicesSorter& slices) + { + Clear(); + + if (!slices.Sort()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "Cannot sort the 3D slices of a DICOM series"); + } + + if (slices.GetSlicesCount() == 0) + { + geometry_.reset(new VolumeImageGeometry); + } + else + { + slices_.reserve(slices.GetSlicesCount()); + slicesRevision_.resize(slices.GetSlicesCount(), 0); + + for (size_t i = 0; i < slices.GetSlicesCount(); i++) + { + const DicomInstanceParameters& slice = + dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i)); + slices_.push_back(new DicomInstanceParameters(slice)); + } + + CheckVolume(); + + const double spacingZ = slices.ComputeSpacingBetweenSlices(); + LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; + + const DicomInstanceParameters& parameters = *slices_[0]; + + geometry_.reset(new VolumeImageGeometry); + geometry_->SetSize(parameters.GetImageInformation().GetWidth(), + parameters.GetImageInformation().GetHeight(), + static_cast<unsigned int>(slices.GetSlicesCount())); + geometry_->SetAxialGeometry(slices.GetSliceGeometry(0)); + geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(), + parameters.GetPixelSpacingY(), spacingZ); + } + } + + + const VolumeImageGeometry& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry() const + { + if (!HasGeometry()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + assert(slices_.size() == geometry_->GetDepth()); + return *geometry_; + } + } + + + const DicomInstanceParameters& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceParameters(size_t index) const + { + CheckSliceIndex(index); + return *slices_[index]; + } + + + uint64_t OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceRevision(size_t index) const + { + CheckSliceIndex(index); + return slicesRevision_[index]; + } + + + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::IncrementSliceRevision(size_t index) + { + CheckSliceIndex(index); + slicesRevision_[index] ++; + } + + + static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command) + { + return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue(); + } + + + void OrthancSeriesVolumeProgressiveLoader::ScheduleNextSliceDownload() + { + assert(strategy_.get() != NULL); + + unsigned int sliceIndex, quality; + + if (strategy_->GetNext(sliceIndex, quality)) + { + assert(quality <= BEST_QUALITY); + + const DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex); + + const std::string& instance = slice.GetOrthancInstanceIdentifier(); + if (instance.empty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::auto_ptr<OracleCommandWithPayload> command; + + if (quality == BEST_QUALITY) + { + std::auto_ptr<GetOrthancImageCommand> tmp(new GetOrthancImageCommand); + tmp->SetHttpHeader("Accept-Encoding", "gzip"); + tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); + tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat()); + tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); + command.reset(tmp.release()); + } + else + { + std::auto_ptr<GetOrthancWebViewerJpegCommand> tmp(new GetOrthancWebViewerJpegCommand); + tmp->SetHttpHeader("Accept-Encoding", "gzip"); + tmp->SetInstance(instance); + tmp->SetQuality((quality == 0 ? 50 : 90)); + tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); + command.reset(tmp.release()); + } + + command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex)); + oracle_.Schedule(*this, command.release()); + } + } + +/** + This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags" +*/ + void OrthancSeriesVolumeProgressiveLoader::LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message) + { + Json::Value body; + message.ParseJsonBody(body); + + if (body.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + { + Json::Value::Members instances = body.getMemberNames(); + + SlicesSorter slices; + + for (size_t i = 0; i < instances.size(); i++) + { + Orthanc::DicomMap dicom; + dicom.FromDicomAsJson(body[instances[i]]); + + std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom)); + instance->SetOrthancInstanceIdentifier(instances[i]); + + // the 3D plane corresponding to the slice + CoordinateSystem3D geometry = instance->GetGeometry(); + slices.AddSlice(geometry, instance.release()); + } + + seriesGeometry_.ComputeGeometry(slices); + } + + size_t slicesCount = seriesGeometry_.GetImageGeometry().GetDepth(); + + if (slicesCount == 0) + { + volume_->Initialize(seriesGeometry_.GetImageGeometry(), Orthanc::PixelFormat_Grayscale8); + } + else + { + const DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0); + + volume_->Initialize(seriesGeometry_.GetImageGeometry(), parameters.GetExpectedPixelFormat()); + volume_->SetDicomParameters(parameters); + volume_->GetPixelData().Clear(); + + strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(static_cast<unsigned int>(slicesCount)), BEST_QUALITY)); + + assert(simultaneousDownloads_ != 0); + for (unsigned int i = 0; i < simultaneousDownloads_; i++) + { + ScheduleNextSliceDownload(); + } + } + + slicesQuality_.resize(slicesCount, 0); + + BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_)); + } + + + void OrthancSeriesVolumeProgressiveLoader::SetSliceContent(unsigned int sliceIndex, + const Orthanc::ImageAccessor& image, + unsigned int quality) + { + assert(sliceIndex < slicesQuality_.size() && + slicesQuality_.size() == volume_->GetPixelData().GetDepth()); + + if (quality >= slicesQuality_[sliceIndex]) + { + { + ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), VolumeProjection_Axial, sliceIndex); + Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image); + } + + volume_->IncrementRevision(); + seriesGeometry_.IncrementSliceRevision(sliceIndex); + slicesQuality_[sliceIndex] = quality; + + BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_)); + } + + ScheduleNextSliceDownload(); + } + + + void OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message) + { + SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY); + } + + + void OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) + { + unsigned int quality; + + switch (message.GetOrigin().GetQuality()) + { + case 50: + quality = LOW_QUALITY; + break; + + case 90: + quality = MIDDLE_QUALITY; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality); + } + + + OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume, + IOracle& oracle, + IObservable& oracleObservable) : + IObserver(oracleObservable.GetBroker()), + IObservable(oracleObservable.GetBroker()), + oracle_(oracle), + active_(false), + simultaneousDownloads_(4), + volume_(volume), + sorter_(new BasicFetchingItemsSorter::Factory) + { + oracleObservable.RegisterObserverCallback( + new Callable<OrthancSeriesVolumeProgressiveLoader, OrthancRestApiCommand::SuccessMessage> + (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry)); + + oracleObservable.RegisterObserverCallback( + new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancImageCommand::SuccessMessage> + (*this, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent)); + + oracleObservable.RegisterObserverCallback( + new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancWebViewerJpegCommand::SuccessMessage> + (*this, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent)); + } + + + void OrthancSeriesVolumeProgressiveLoader::SetSimultaneousDownloads(unsigned int count) + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else if (count == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + simultaneousDownloads_ = count; + } + } + + + void OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId) + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + active_ = true; + + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetUri("/series/" + seriesId + "/instances-tags"); + + oracle_.Schedule(*this, command.release()); + } + } + + + IVolumeSlicer::IExtractedSlice* + OrthancSeriesVolumeProgressiveLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane) + { + if (volume_->HasGeometry()) + { + return new ExtractedSlice(*this, cuttingPlane); + } + else + { + return new IVolumeSlicer::InvalidSlice; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,134 @@ +/** + * 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 "../Messages/IObservable.h" +#include "../Messages/IObserver.h" +#include "../Oracle/GetOrthancImageCommand.h" +#include "../Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../Oracle/IOracle.h" +#include "../Oracle/OrthancRestApiCommand.h" +#include "../Toolbox/SlicesSorter.h" +#include "../Volumes/DicomVolumeImage.h" +#include "../Volumes/IVolumeSlicer.h" +#include "IFetchingItemsSorter.h" +#include "IFetchingStrategy.h" + +#include <boost/shared_ptr.hpp> + +namespace OrthancStone +{ + /** + This class is used to manage the progressive loading of a volume that + is stored in a Dicom series. + */ + class OrthancSeriesVolumeProgressiveLoader : + public IObserver, + public IObservable, + public IVolumeSlicer + { + private: + static const unsigned int LOW_QUALITY = 0; + static const unsigned int MIDDLE_QUALITY = 1; + static const unsigned int BEST_QUALITY = 2; + + class ExtractedSlice; + + /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */ + class SeriesGeometry : public boost::noncopyable + { + private: + void CheckSlice(size_t index, + const DicomInstanceParameters& reference) const; + + void CheckVolume() const; + + void Clear(); + + void CheckSliceIndex(size_t index) const; + + std::auto_ptr<VolumeImageGeometry> geometry_; + std::vector<DicomInstanceParameters*> slices_; + std::vector<uint64_t> slicesRevision_; + + public: + ~SeriesGeometry() + { + Clear(); + } + + void ComputeGeometry(SlicesSorter& slices); + + bool HasGeometry() const + { + return geometry_.get() != NULL; + } + + const VolumeImageGeometry& GetImageGeometry() const; + + const DicomInstanceParameters& GetSliceParameters(size_t index) const; + + uint64_t GetSliceRevision(size_t index) const; + + void IncrementSliceRevision(size_t index); + }; + + + void ScheduleNextSliceDownload(); + + void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message); + + void SetSliceContent(unsigned int sliceIndex, + const Orthanc::ImageAccessor& image, + unsigned int quality); + + void LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message); + + void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message); + + IOracle& oracle_; + bool active_; + unsigned int simultaneousDownloads_; + SeriesGeometry seriesGeometry_; + boost::shared_ptr<DicomVolumeImage> volume_; + std::auto_ptr<IFetchingItemsSorter::IFactory> sorter_; + std::auto_ptr<IFetchingStrategy> strategy_; + std::vector<unsigned int> slicesQuality_; + + + public: + OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume, + IOracle& oracle, + IObservable& oracleObservable); + + void SetSimultaneousDownloads(unsigned int count); + + void LoadSeries(const std::string& seriesId); + + /** + When a slice is requested, the strategy algorithm (that defines the + sequence of resources to be loaded from the server) is modified to + take into account this request (this is done in the ExtractedSlice ctor) + */ + virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/IMessageEmitter.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,45 @@ +/** + * 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 "IObserver.h" +#include "IMessage.h" + +namespace OrthancStone +{ + /** + This class may be used to customize the way the messages are sent between + a source and a destination, for instance by the ThreadedOracle. + + See the concrete class LockingEmitter for an example of when it is useful. + */ + class IMessageEmitter : public boost::noncopyable + { + public: + virtual ~IMessageEmitter() + { + } + + virtual void EmitMessage(const IObserver& observer, + const IMessage& message) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/LockingEmitter.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,111 @@ +/** + * 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 "IMessageEmitter.h" +#include "IObservable.h" + +#include <boost/thread.hpp> + +namespace OrthancStone +{ + /** + * This class is used when using the ThreadedOracle : since messages + * can be sent from multiple Oracle threads, this IMessageEmitter + * implementation serializes the callbacks. + * + * The internal mutex used in Oracle messaging can also be used to + * protect the application data. Thus, this class can be used as a single + * application-wide mutex. + */ + class LockingEmitter : public IMessageEmitter + { + private: + boost::shared_mutex mutex_; + MessageBroker broker_; + IObservable oracleObservable_; + + public: + LockingEmitter() : + oracleObservable_(broker_) + { + } + + MessageBroker& GetBroker() + { + return broker_; + } + + virtual void EmitMessage(const IObserver& observer, + const IMessage& message) ORTHANC_OVERRIDE + { + try + { + boost::unique_lock<boost::shared_mutex> lock(mutex_); + oracleObservable_.EmitMessage(observer, message); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while emitting a message: " << e.What(); + } + } + + + class ReaderLock : public boost::noncopyable + { + private: + LockingEmitter& that_; + boost::shared_lock<boost::shared_mutex> lock_; + + public: + ReaderLock(LockingEmitter& that) : + that_(that), + lock_(that.mutex_) + { + } + }; + + + class WriterLock : public boost::noncopyable + { + private: + LockingEmitter& that_; + boost::unique_lock<boost::shared_mutex> lock_; + + public: + WriterLock(LockingEmitter& that) : + that_(that), + lock_(that.mutex_) + { + } + + MessageBroker& GetBroker() + { + return that_.broker_; + } + + IObservable& GetOracleObservable() + { + return that_.oracleObservable_; + } + }; + }; +}
--- a/Framework/Messages/MessageBroker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Messages/MessageBroker.h Mon Jun 24 14:35:00 2019 +0200 @@ -18,8 +18,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ +#pragma once -#pragma once +#include "../StoneException.h" #include "boost/noncopyable.hpp" @@ -40,6 +41,10 @@ std::set<const IObserver*> activeObservers_; // the list of observers that are currently alive (that have not been deleted) public: + MessageBroker() + { + } + void Register(const IObserver& observer) { activeObservers_.insert(&observer);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GetOrthancImageCommand.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,153 @@ +/** + * 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 "GetOrthancImageCommand.h" + +#include <Core/Images/JpegReader.h> +#include <Core/Images/PamReader.h> +#include <Core/Images/PngReader.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +namespace OrthancStone +{ + GetOrthancImageCommand::SuccessMessage::SuccessMessage(const GetOrthancImageCommand& command, + Orthanc::ImageAccessor* image, // Takes ownership + Orthanc::MimeType mime) : + OriginMessage(command), + image_(image), + mime_(mime) + { + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + + GetOrthancImageCommand::GetOrthancImageCommand() : + uri_("/"), + timeout_(60), + hasExpectedFormat_(false) + { + } + + + void GetOrthancImageCommand::SetExpectedPixelFormat(Orthanc::PixelFormat format) + { + hasExpectedFormat_ = true; + expectedFormat_ = format; + } + + + void GetOrthancImageCommand::SetInstanceUri(const std::string& instance, + Orthanc::PixelFormat pixelFormat) + { + uri_ = "/instances/" + instance; + + switch (pixelFormat) + { + case Orthanc::PixelFormat_RGB24: + uri_ += "/preview"; + break; + + case Orthanc::PixelFormat_Grayscale16: + uri_ += "/image-uint16"; + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + uri_ += "/image-int16"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + void GetOrthancImageCommand::ProcessHttpAnswer(IMessageEmitter& emitter, + const IObserver& receiver, + const std::string& answer, + const HttpHeaders& answerHeaders) const + { + Orthanc::MimeType contentType = Orthanc::MimeType_Binary; + + for (HttpHeaders::const_iterator it = answerHeaders.begin(); + it != answerHeaders.end(); ++it) + { + std::string s; + Orthanc::Toolbox::ToLowerCase(s, it->first); + + if (s == "content-type") + { + contentType = Orthanc::StringToMimeType(it->second); + break; + } + } + + std::auto_ptr<Orthanc::ImageAccessor> image; + + switch (contentType) + { + case Orthanc::MimeType_Png: + { + image.reset(new Orthanc::PngReader); + dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer); + break; + } + + case Orthanc::MimeType_Pam: + { + image.reset(new Orthanc::PamReader); + dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer); + break; + } + + case Orthanc::MimeType_Jpeg: + { + image.reset(new Orthanc::JpegReader); + dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "Unsupported HTTP Content-Type for an image: " + + std::string(Orthanc::EnumerationToString(contentType))); + } + + if (hasExpectedFormat_) + { + if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 && + image->GetFormat() == Orthanc::PixelFormat_Grayscale16) + { + image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + } + + if (expectedFormat_ != image->GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + } + + SuccessMessage message(*this, image.release(), contentType); + emitter.EmitMessage(receiver, message); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GetOrthancImageCommand.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,119 @@ +/** + * 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 "../Messages/IMessageEmitter.h" +#include "OracleCommandWithPayload.h" + +#include <Core/Images/ImageAccessor.h> + +#include <map> + +namespace OrthancStone +{ + class GetOrthancImageCommand : public OracleCommandWithPayload + { + public: + typedef std::map<std::string, std::string> HttpHeaders; + + class SuccessMessage : public OriginMessage<GetOrthancImageCommand> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + std::auto_ptr<Orthanc::ImageAccessor> image_; + Orthanc::MimeType mime_; + + public: + SuccessMessage(const GetOrthancImageCommand& command, + Orthanc::ImageAccessor* image, // Takes ownership + Orthanc::MimeType mime); + + const Orthanc::ImageAccessor& GetImage() const + { + return *image_; + } + + Orthanc::MimeType GetMimeType() const + { + return mime_; + } + }; + + + private: + std::string uri_; + HttpHeaders headers_; + unsigned int timeout_; + bool hasExpectedFormat_; + Orthanc::PixelFormat expectedFormat_; + + public: + GetOrthancImageCommand(); + + virtual Type GetType() const + { + return Type_GetOrthancImage; + } + + void SetExpectedPixelFormat(Orthanc::PixelFormat format); + + void SetUri(const std::string& uri) + { + uri_ = uri; + } + + void SetInstanceUri(const std::string& instance, + Orthanc::PixelFormat pixelFormat); + + void SetHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + const std::string& GetUri() const + { + return uri_; + } + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int seconds) + { + timeout_ = seconds; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + + void ProcessHttpAnswer(IMessageEmitter& emitter, + const IObserver& receiver, + const std::string& answer, + const HttpHeaders& answerHeaders) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,217 @@ +/** + * 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 "GetOrthancWebViewerJpegCommand.h" + +#include "../Toolbox/LinearAlgebra.h" + +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Images/JpegReader.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +#include <json/reader.h> +#include <json/value.h> + +namespace OrthancStone +{ + GetOrthancWebViewerJpegCommand::SuccessMessage::SuccessMessage(const GetOrthancWebViewerJpegCommand& command, + Orthanc::ImageAccessor* image) : // Takes ownership + OriginMessage(command), + image_(image) + { + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + + GetOrthancWebViewerJpegCommand::GetOrthancWebViewerJpegCommand() : + frame_(0), + quality_(95), + timeout_(60), + expectedFormat_(Orthanc::PixelFormat_Grayscale8) + { + } + + + void GetOrthancWebViewerJpegCommand::SetQuality(unsigned int quality) + { + if (quality <= 0 || + quality > 100) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + quality_ = quality; + } + } + + + std::string GetOrthancWebViewerJpegCommand::GetUri() const + { + return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) + + "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_)); + } + + + void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(IMessageEmitter& emitter, + const IObserver& receiver, + const std::string& answer) const + { + // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()" + + Json::Value encoded; + + { + Json::Reader reader; + if (!reader.parse(answer, encoded)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + if (encoded.type() != Json::objectValue || + !encoded.isMember("Orthanc") || + encoded["Orthanc"].type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + const Json::Value& info = encoded["Orthanc"]; + if (!info.isMember("PixelData") || + !info.isMember("Stretched") || + !info.isMember("Compression") || + info["Compression"].type() != Json::stringValue || + info["PixelData"].type() != Json::stringValue || + info["Stretched"].type() != Json::booleanValue || + info["Compression"].asString() != "Jpeg") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + bool isSigned = false; + bool isStretched = info["Stretched"].asBool(); + + if (info.isMember("IsSigned")) + { + if (info["IsSigned"].type() != Json::booleanValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + isSigned = info["IsSigned"].asBool(); + } + } + + std::auto_ptr<Orthanc::ImageAccessor> reader; + + { + std::string jpeg; + Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); + + reader.reset(new Orthanc::JpegReader); + dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg); + } + + if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image + { + if (expectedFormat_ != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (isSigned || isStretched) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + SuccessMessage message(*this, reader.release()); + emitter.EmitMessage(receiver, message); + return; + } + } + + if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (!isStretched) + { + if (expectedFormat_ != reader->GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + SuccessMessage message(*this, reader.release()); + emitter.EmitMessage(receiver, message); + return; + } + } + + int32_t stretchLow = 0; + int32_t stretchHigh = 0; + + if (!info.isMember("StretchLow") || + !info.isMember("StretchHigh") || + info["StretchLow"].type() != Json::intValue || + info["StretchHigh"].type() != Json::intValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + stretchLow = info["StretchLow"].asInt(); + stretchHigh = info["StretchHigh"].asInt(); + + if (stretchLow < -32768 || + stretchHigh > 65535 || + (stretchLow < 0 && stretchHigh > 32767)) + { + // This range cannot be represented with a uint16_t or an int16_t + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // Decode a grayscale JPEG 8bpp image coming from the Web viewer + std::auto_ptr<Orthanc::ImageAccessor> image + (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false)); + + Orthanc::ImageProcessing::Convert(*image, *reader); + reader.reset(); + + float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; + + if (!LinearAlgebra::IsCloseToZero(scaling)) + { + float offset = static_cast<float>(stretchLow) / scaling; + Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); + } + + SuccessMessage message(*this, image.release()); + emitter.EmitMessage(receiver, message); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,135 @@ +/** + * 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 "../Messages/IMessageEmitter.h" +#include "OracleCommandWithPayload.h" + +#include <Core/Images/ImageAccessor.h> + +#include <map> + +namespace OrthancStone +{ + class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload + { + public: + typedef std::map<std::string, std::string> HttpHeaders; + + class SuccessMessage : public OriginMessage<GetOrthancWebViewerJpegCommand> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + std::auto_ptr<Orthanc::ImageAccessor> image_; + + public: + SuccessMessage(const GetOrthancWebViewerJpegCommand& command, + Orthanc::ImageAccessor* image); // Takes ownership + + const Orthanc::ImageAccessor& GetImage() const + { + return *image_; + } + }; + + private: + std::string instanceId_; + unsigned int frame_; + unsigned int quality_; + HttpHeaders headers_; + unsigned int timeout_; + Orthanc::PixelFormat expectedFormat_; + + public: + GetOrthancWebViewerJpegCommand(); + + virtual Type GetType() const + { + return Type_GetOrthancWebViewerJpeg; + } + + void SetExpectedPixelFormat(Orthanc::PixelFormat format) + { + expectedFormat_ = format; + } + + void SetInstance(const std::string& instanceId) + { + instanceId_ = instanceId; + } + + void SetFrame(unsigned int frame) + { + frame_ = frame; + } + + void SetQuality(unsigned int quality); + + void SetHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + Orthanc::PixelFormat GetExpectedPixelFormat() const + { + return expectedFormat_; + } + + const std::string& GetInstanceId() const + { + return instanceId_; + } + + unsigned int GetFrame() const + { + return frame_; + } + + unsigned int GetQuality() const + { + return quality_; + } + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int seconds) + { + timeout_ = seconds; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + + std::string GetUri() const; + + void ProcessHttpAnswer(IMessageEmitter& emitter, + const IObserver& receiver, + const std::string& answer) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/IOracle.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,39 @@ +/** + * 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 "../Messages/IObserver.h" +#include "IOracleCommand.h" + +namespace OrthancStone +{ + class IOracle : public boost::noncopyable + { + public: + virtual ~IOracle() + { + } + + virtual void Schedule(const IObserver& receiver, + IOracleCommand* command) = 0; // Takes ownership + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/IOracleCommand.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,45 @@ +/** + * 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 IOracleCommand : public boost::noncopyable + { + public: + enum Type + { + Type_Sleep, + Type_OrthancRestApi, + Type_GetOrthancImage, + Type_GetOrthancWebViewerJpeg + }; + + virtual ~IOracleCommand() + { + } + + virtual Type GetType() const = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OracleCommandExceptionMessage.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,64 @@ +/** + * 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 "../Messages/IMessage.h" +#include "IOracleCommand.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + class OracleCommandExceptionMessage : public IMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const IOracleCommand& command_; + Orthanc::OrthancException exception_; + + public: + OracleCommandExceptionMessage(const IOracleCommand& command, + const Orthanc::OrthancException& exception) : + command_(command), + exception_(exception) + { + } + + OracleCommandExceptionMessage(const IOracleCommand& command, + const Orthanc::ErrorCode& error) : + command_(command), + exception_(error) + { + } + + const IOracleCommand& GetCommand() const + { + return command_; + } + + const Orthanc::OrthancException& GetException() const + { + return exception_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OracleCommandWithPayload.cpp Mon Jun 24 14:35: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/>. + **/ + + +#include "OracleCommandWithPayload.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + void OracleCommandWithPayload::SetPayload(Orthanc::IDynamicObject* payload) + { + if (payload == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + payload_.reset(payload); + } + } + + + Orthanc::IDynamicObject& OracleCommandWithPayload::GetPayload() const + { + if (HasPayload()) + { + return *payload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + Orthanc::IDynamicObject* OracleCommandWithPayload::ReleasePayload() + { + if (HasPayload()) + { + return payload_.release(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OracleCommandWithPayload.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,49 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 "IOracleCommand.h" + +#include <Core/IDynamicObject.h> + +#include <memory> + +namespace OrthancStone +{ + class OracleCommandWithPayload : public IOracleCommand + { + private: + std::auto_ptr<Orthanc::IDynamicObject> payload_; + + public: + void SetPayload(Orthanc::IDynamicObject* payload); + + bool HasPayload() const + { + return (payload_.get() != NULL); + } + + Orthanc::IDynamicObject& GetPayload() const; + + Orthanc::IDynamicObject* ReleasePayload(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OrthancRestApiCommand.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,78 @@ +/** + * 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 "OrthancRestApiCommand.h" + +#include <Core/OrthancException.h> + +#include <json/reader.h> +#include <json/writer.h> + +namespace OrthancStone +{ + OrthancRestApiCommand::SuccessMessage::SuccessMessage(const OrthancRestApiCommand& command, + const HttpHeaders& answerHeaders, + std::string& answer) : + OriginMessage(command), + headers_(answerHeaders), + answer_(answer) + { + } + + + void OrthancRestApiCommand::SuccessMessage::ParseJsonBody(Json::Value& target) const + { + Json::Reader reader; + if (!reader.parse(answer_, target)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + OrthancRestApiCommand::OrthancRestApiCommand() : + method_(Orthanc::HttpMethod_Get), + uri_("/"), + timeout_(60) + { + } + + + void OrthancRestApiCommand::SetBody(const Json::Value& json) + { + Json::FastWriter writer; + body_ = writer.write(json); + } + + + const std::string& OrthancRestApiCommand::GetBody() const + { + if (method_ == Orthanc::HttpMethod_Post || + method_ == Orthanc::HttpMethod_Put) + { + return body_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OrthancRestApiCommand.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,136 @@ +/** + * 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 "../Messages/IMessage.h" +#include "OracleCommandWithPayload.h" + +#include <Core/Enumerations.h> + +#include <map> +#include <json/value.h> + +namespace OrthancStone +{ + class OrthancRestApiCommand : public OracleCommandWithPayload + { + public: + typedef std::map<std::string, std::string> HttpHeaders; + + class SuccessMessage : public OriginMessage<OrthancRestApiCommand> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + HttpHeaders headers_; + std::string answer_; + + public: + SuccessMessage(const OrthancRestApiCommand& command, + const HttpHeaders& answerHeaders, + std::string& answer /* will be swapped to avoid a memcpy() */); + + const std::string& GetAnswer() const + { + return answer_; + } + + void ParseJsonBody(Json::Value& target) const; + + const HttpHeaders& GetAnswerHeaders() const + { + return headers_; + } + }; + + + private: + Orthanc::HttpMethod method_; + std::string uri_; + std::string body_; + HttpHeaders headers_; + unsigned int timeout_; + + public: + OrthancRestApiCommand(); + + virtual Type GetType() const + { + return Type_OrthancRestApi; + } + + void SetMethod(Orthanc::HttpMethod method) + { + method_ = method; + } + + void SetUri(const std::string& uri) + { + uri_ = uri; + } + + void SetBody(const std::string& body) + { + body_ = body; + } + + void SetBody(const Json::Value& json); + + void SwapBody(std::string& body) + { + body_.swap(body); + } + + void SetHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + Orthanc::HttpMethod GetMethod() const + { + return method_; + } + + const std::string& GetUri() const + { + return uri_; + } + + const std::string& GetBody() const; + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int seconds) + { + timeout_ = seconds; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/SleepOracleCommand.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,52 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 "../Messages/IMessage.h" +#include "OracleCommandWithPayload.h" + +namespace OrthancStone +{ + class SleepOracleCommand : public OracleCommandWithPayload + { + private: + unsigned int milliseconds_; + + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, TimeoutMessage, SleepOracleCommand); + + SleepOracleCommand(unsigned int milliseconds) : + milliseconds_(milliseconds) + { + } + + virtual Type GetType() const + { + return Type_Sleep; + } + + unsigned int GetDelay() const + { + return milliseconds_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ThreadedOracle.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,533 @@ +/** + * 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 "ThreadedOracle.h" + +#include "GetOrthancImageCommand.h" +#include "GetOrthancWebViewerJpegCommand.h" +#include "OrthancRestApiCommand.h" +#include "SleepOracleCommand.h" +#include "OracleCommandExceptionMessage.h" + +#include <Core/Compression/GzipCompressor.h> +#include <Core/HttpClient.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + + +namespace OrthancStone +{ + class ThreadedOracle::Item : public Orthanc::IDynamicObject + { + private: + const IObserver& receiver_; + std::auto_ptr<IOracleCommand> command_; + + public: + Item(const IObserver& receiver, + IOracleCommand* command) : + receiver_(receiver), + command_(command) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + const IObserver& GetReceiver() const + { + return receiver_; + } + + IOracleCommand& GetCommand() + { + assert(command_.get() != NULL); + return *command_; + } + }; + + + class ThreadedOracle::SleepingCommands : public boost::noncopyable + { + private: + class Item + { + private: + const IObserver& receiver_; + std::auto_ptr<SleepOracleCommand> command_; + boost::posix_time::ptime expiration_; + + public: + Item(const IObserver& receiver, + SleepOracleCommand* command) : + receiver_(receiver), + command_(command) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + expiration_ = (boost::posix_time::microsec_clock::local_time() + + boost::posix_time::milliseconds(command_->GetDelay())); + } + + const boost::posix_time::ptime& GetExpirationTime() const + { + return expiration_; + } + + void Awake(IMessageEmitter& emitter) + { + assert(command_.get() != NULL); + + SleepOracleCommand::TimeoutMessage message(*command_); + emitter.EmitMessage(receiver_, message); + } + }; + + typedef std::list<Item*> Content; + + boost::mutex mutex_; + Content content_; + + public: + ~SleepingCommands() + { + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + if (*it != NULL) + { + delete *it; + } + } + } + + void Add(const IObserver& receiver, + SleepOracleCommand* command) // Takes ownership + { + boost::mutex::scoped_lock lock(mutex_); + + content_.push_back(new Item(receiver, command)); + } + + void AwakeExpired(IMessageEmitter& emitter) + { + boost::mutex::scoped_lock lock(mutex_); + + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time(); + + Content stillSleeping; + + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + if (*it != NULL && + (*it)->GetExpirationTime() <= now) + { + (*it)->Awake(emitter); + delete *it; + *it = NULL; + } + else + { + stillSleeping.push_back(*it); + } + } + + // Compact the still-sleeping commands + content_ = stillSleeping; + } + }; + + + static void CopyHttpHeaders(Orthanc::HttpClient& client, + const Orthanc::HttpClient::HttpHeaders& headers) + { + for (Orthanc::HttpClient::HttpHeaders::const_iterator + it = headers.begin(); it != headers.end(); it++ ) + { + client.AddHeader(it->first, it->second); + } + } + + + static void DecodeAnswer(std::string& answer, + const Orthanc::HttpClient::HttpHeaders& headers) + { + Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None; + + for (Orthanc::HttpClient::HttpHeaders::const_iterator it = headers.begin(); + it != headers.end(); ++it) + { + std::string s; + Orthanc::Toolbox::ToLowerCase(s, it->first); + + if (s == "content-encoding") + { + if (it->second == "gzip") + { + contentEncoding = Orthanc::HttpCompression_Gzip; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "Unsupported HTTP Content-Encoding: " + it->second); + } + + break; + } + } + + if (contentEncoding == Orthanc::HttpCompression_Gzip) + { + std::string compressed; + answer.swap(compressed); + + Orthanc::GzipCompressor compressor; + compressor.Uncompress(answer, compressed.c_str(), compressed.size()); + + LOG(INFO) << "Uncompressing gzip Encoding: from " << compressed.size() + << " to " << answer.size() << " bytes"; + } + } + + + static void Execute(IMessageEmitter& emitter, + const Orthanc::WebServiceParameters& orthanc, + const IObserver& receiver, + const OrthancRestApiCommand& command) + { + Orthanc::HttpClient client(orthanc, command.GetUri()); + client.SetMethod(command.GetMethod()); + client.SetTimeout(command.GetTimeout()); + + CopyHttpHeaders(client, command.GetHttpHeaders()); + + if (command.GetMethod() == Orthanc::HttpMethod_Post || + command.GetMethod() == Orthanc::HttpMethod_Put) + { + client.SetBody(command.GetBody()); + } + + std::string answer; + Orthanc::HttpClient::HttpHeaders answerHeaders; + client.ApplyAndThrowException(answer, answerHeaders); + + DecodeAnswer(answer, answerHeaders); + + OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer); + emitter.EmitMessage(receiver, message); + } + + + static void Execute(IMessageEmitter& emitter, + const Orthanc::WebServiceParameters& orthanc, + const IObserver& receiver, + const GetOrthancImageCommand& command) + { + Orthanc::HttpClient client(orthanc, command.GetUri()); + client.SetTimeout(command.GetTimeout()); + + CopyHttpHeaders(client, command.GetHttpHeaders()); + + std::string answer; + Orthanc::HttpClient::HttpHeaders answerHeaders; + client.ApplyAndThrowException(answer, answerHeaders); + + DecodeAnswer(answer, answerHeaders); + + command.ProcessHttpAnswer(emitter, receiver, answer, answerHeaders); + } + + + static void Execute(IMessageEmitter& emitter, + const Orthanc::WebServiceParameters& orthanc, + const IObserver& receiver, + const GetOrthancWebViewerJpegCommand& command) + { + Orthanc::HttpClient client(orthanc, command.GetUri()); + client.SetTimeout(command.GetTimeout()); + + CopyHttpHeaders(client, command.GetHttpHeaders()); + + std::string answer; + Orthanc::HttpClient::HttpHeaders answerHeaders; + client.ApplyAndThrowException(answer, answerHeaders); + + DecodeAnswer(answer, answerHeaders); + + command.ProcessHttpAnswer(emitter, receiver, answer); + } + + + void ThreadedOracle::Step() + { + std::auto_ptr<Orthanc::IDynamicObject> object(queue_.Dequeue(100)); + + if (object.get() != NULL) + { + Item& item = dynamic_cast<Item&>(*object); + + try + { + switch (item.GetCommand().GetType()) + { + case IOracleCommand::Type_Sleep: + { + SleepOracleCommand& command = dynamic_cast<SleepOracleCommand&>(item.GetCommand()); + + std::auto_ptr<SleepOracleCommand> copy(new SleepOracleCommand(command.GetDelay())); + + if (command.HasPayload()) + { + copy->SetPayload(command.ReleasePayload()); + } + + sleepingCommands_->Add(item.GetReceiver(), copy.release()); + + break; + } + + case IOracleCommand::Type_OrthancRestApi: + Execute(emitter_, orthanc_, item.GetReceiver(), + dynamic_cast<const OrthancRestApiCommand&>(item.GetCommand())); + break; + + case IOracleCommand::Type_GetOrthancImage: + Execute(emitter_, orthanc_, item.GetReceiver(), + dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand())); + break; + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + Execute(emitter_, orthanc_, item.GetReceiver(), + dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand())); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception within the oracle: " << e.What(); + emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e)); + } + catch (...) + { + LOG(ERROR) << "Threaded exception within the oracle"; + emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage + (item.GetCommand(), Orthanc::ErrorCode_InternalError)); + } + } + } + + + void ThreadedOracle::Worker(ThreadedOracle* that) + { + assert(that != NULL); + + for (;;) + { + { + boost::mutex::scoped_lock lock(that->mutex_); + if (that->state_ != State_Running) + { + return; + } + } + + that->Step(); + } + } + + + void ThreadedOracle::SleepingWorker(ThreadedOracle* that) + { + assert(that != NULL); + + for (;;) + { + { + boost::mutex::scoped_lock lock(that->mutex_); + if (that->state_ != State_Running) + { + return; + } + } + + that->sleepingCommands_->AwakeExpired(that->emitter_); + + boost::this_thread::sleep(boost::posix_time::milliseconds(that->sleepingTimeResolution_)); + } + } + + + void ThreadedOracle::StopInternal() + { + { + boost::mutex::scoped_lock lock(mutex_); + + if (state_ == State_Setup || + state_ == State_Stopped) + { + return; + } + else + { + state_ = State_Stopped; + } + } + + if (sleepingWorker_.joinable()) + { + sleepingWorker_.join(); + } + + for (size_t i = 0; i < workers_.size(); i++) + { + if (workers_[i] != NULL) + { + if (workers_[i]->joinable()) + { + workers_[i]->join(); + } + + delete workers_[i]; + } + } + } + + + ThreadedOracle::ThreadedOracle(IMessageEmitter& emitter) : + emitter_(emitter), + state_(State_Setup), + workers_(4), + sleepingCommands_(new SleepingCommands), + sleepingTimeResolution_(50) // By default, time resolution of 50ms + { + } + + + ThreadedOracle::~ThreadedOracle() + { + if (state_ == State_Running) + { + LOG(ERROR) << "The threaded oracle is still running, explicit call to " + << "Stop() is mandatory to avoid crashes"; + } + + try + { + StopInternal(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while stopping the threaded oracle: " << e.What(); + } + catch (...) + { + LOG(ERROR) << "Native exception while stopping the threaded oracle"; + } + } + + + void ThreadedOracle::SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc) + { + boost::mutex::scoped_lock lock(mutex_); + + if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + orthanc_ = orthanc; + } + } + + + void ThreadedOracle::SetThreadsCount(unsigned int count) + { + boost::mutex::scoped_lock lock(mutex_); + + if (count <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + workers_.resize(count); + } + } + + + void ThreadedOracle::SetSleepingTimeResolution(unsigned int milliseconds) + { + boost::mutex::scoped_lock lock(mutex_); + + if (milliseconds <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + sleepingTimeResolution_ = milliseconds; + } + } + + + void ThreadedOracle::Start() + { + boost::mutex::scoped_lock lock(mutex_); + + if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + state_ = State_Running; + + for (unsigned int i = 0; i < workers_.size(); i++) + { + workers_[i] = new boost::thread(Worker, this); + } + + sleepingWorker_ = boost::thread(SleepingWorker, this); + } + } + + + void ThreadedOracle::Schedule(const IObserver& receiver, + IOracleCommand* command) + { + queue_.Enqueue(new Item(receiver, command)); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ThreadedOracle.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,94 @@ +/** + * 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 + +#if !defined(ORTHANC_ENABLE_THREADS) +# error The macro ORTHANC_ENABLE_THREADS must be defined +#endif + +#if ORTHANC_ENABLE_THREADS != 1 +# error This file can only compiled for native targets +#endif + +#include "../Messages/IMessageEmitter.h" +#include "IOracle.h" + +#include <Core/WebServiceParameters.h> +#include <Core/MultiThreading/SharedMessageQueue.h> + + +namespace OrthancStone +{ + class ThreadedOracle : public IOracle + { + private: + enum State + { + State_Setup, + State_Running, + State_Stopped + }; + + class Item; + class SleepingCommands; + + IMessageEmitter& emitter_; + Orthanc::WebServiceParameters orthanc_; + Orthanc::SharedMessageQueue queue_; + State state_; + boost::mutex mutex_; + std::vector<boost::thread*> workers_; + boost::shared_ptr<SleepingCommands> sleepingCommands_; + boost::thread sleepingWorker_; + unsigned int sleepingTimeResolution_; + + void Step(); + + static void Worker(ThreadedOracle* that); + + static void SleepingWorker(ThreadedOracle* that); + + void StopInternal(); + + public: + ThreadedOracle(IMessageEmitter& emitter); + + virtual ~ThreadedOracle(); + + // The reference is not stored. + void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc); + + void SetThreadsCount(unsigned int count); + + void SetSleepingTimeResolution(unsigned int milliseconds); + + void Start(); + + void Stop() + { + StopInternal(); + } + + virtual void Schedule(const IObserver& receiver, + IOracleCommand* command); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/WebAssemblyOracle.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,479 @@ +/** + * 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 "WebAssemblyOracle.h" + +#include "SleepOracleCommand.h" + +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +#include <emscripten.h> +#include <emscripten/html5.h> +#include <emscripten/fetch.h> + + +namespace OrthancStone +{ + class WebAssemblyOracle::TimeoutContext + { + private: + WebAssemblyOracle& oracle_; + const IObserver& receiver_; + std::auto_ptr<SleepOracleCommand> command_; + + public: + TimeoutContext(WebAssemblyOracle& oracle, + const IObserver& receiver, + IOracleCommand* command) : + oracle_(oracle), + receiver_(receiver) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + command_.reset(dynamic_cast<SleepOracleCommand*>(command)); + } + } + + void EmitMessage() + { + SleepOracleCommand::TimeoutMessage message(*command_); + oracle_.EmitMessage(receiver_, message); + } + + static void Callback(void *userData) + { + std::auto_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData)); + context->EmitMessage(); + } + }; + + + class WebAssemblyOracle::Emitter : public IMessageEmitter + { + private: + WebAssemblyOracle& oracle_; + + public: + Emitter(WebAssemblyOracle& oracle) : + oracle_(oracle) + { + } + + virtual void EmitMessage(const IObserver& receiver, + const IMessage& message) + { + oracle_.EmitMessage(receiver, message); + } + }; + + + class WebAssemblyOracle::FetchContext : public boost::noncopyable + { + private: + Emitter emitter_; + const IObserver& receiver_; + std::auto_ptr<IOracleCommand> command_; + std::string expectedContentType_; + + public: + FetchContext(WebAssemblyOracle& oracle, + const IObserver& receiver, + IOracleCommand* command, + const std::string& expectedContentType) : + emitter_(oracle), + receiver_(receiver), + command_(command), + expectedContentType_(expectedContentType) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + const std::string& GetExpectedContentType() const + { + return expectedContentType_; + } + + void EmitMessage(const IMessage& message) + { + emitter_.EmitMessage(receiver_, message); + } + + IMessageEmitter& GetEmitter() + { + return emitter_; + } + + const IObserver& GetReceiver() const + { + return receiver_; + } + + IOracleCommand& GetCommand() const + { + return *command_; + } + + template <typename T> + const T& GetTypedCommand() const + { + return dynamic_cast<T&>(*command_); + } + + static void SuccessCallback(emscripten_fetch_t *fetch) + { + /** + * Firstly, make a local copy of the fetched information, and + * free data associated with the fetch. + **/ + + std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); + + std::string answer; + if (fetch->numBytes > 0) + { + answer.assign(fetch->data, fetch->numBytes); + } + + /** + * TODO - HACK - As of emscripten-1.38.31, the fetch API does + * not contain a way to retrieve the HTTP headers of the + * answer. We make the assumption that the "Content-Type" header + * of the response is the same as the "Accept" header of the + * query. This should be fixed in future versions of emscripten. + * https://github.com/emscripten-core/emscripten/pull/8486 + **/ + + HttpHeaders headers; + if (!context->GetExpectedContentType().empty()) + { + headers["Content-Type"] = context->GetExpectedContentType(); + } + + + emscripten_fetch_close(fetch); + + + /** + * Secondly, use the retrieved data. + **/ + + try + { + if (context.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + switch (context->GetCommand().GetType()) + { + case IOracleCommand::Type_OrthancRestApi: + { + OrthancRestApiCommand::SuccessMessage message + (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer); + context->EmitMessage(message); + break; + } + + case IOracleCommand::Type_GetOrthancImage: + { + context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer + (context->GetEmitter(), context->GetReceiver(), answer, headers); + break; + } + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + { + context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer + (context->GetEmitter(), context->GetReceiver(), answer); + break; + } + + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " + << context->GetCommand().GetType(); + } + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Error while processing a fetch answer in the oracle: " << e.What(); + } + } + + static void FailureCallback(emscripten_fetch_t *fetch) + { + std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); + + LOG(ERROR) << "Fetching " << fetch->url << " failed, HTTP failure status code: " << fetch->status; + + /** + * TODO - The following code leads to an infinite recursion, at + * least with Firefox running on incognito mode => WHY? + **/ + //emscripten_fetch_close(fetch); // Also free data on failure. + } + }; + + + + class WebAssemblyOracle::FetchCommand : public boost::noncopyable + { + private: + WebAssemblyOracle& oracle_; + const IObserver& receiver_; + std::auto_ptr<IOracleCommand> command_; + Orthanc::HttpMethod method_; + std::string uri_; + std::string body_; + HttpHeaders headers_; + unsigned int timeout_; + std::string expectedContentType_; + + public: + FetchCommand(WebAssemblyOracle& oracle, + const IObserver& receiver, + IOracleCommand* command) : + oracle_(oracle), + receiver_(receiver), + command_(command), + method_(Orthanc::HttpMethod_Get), + timeout_(0) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + void SetMethod(Orthanc::HttpMethod method) + { + method_ = method; + } + + void SetUri(const std::string& uri) + { + uri_ = oracle_.orthancRoot_ + uri; + } + + void SetBody(std::string& body /* will be swapped */) + { + body_.swap(body); + } + + void SetHttpHeaders(const HttpHeaders& headers) + { + headers_ = headers; + } + + void SetTimeout(unsigned int timeout) + { + timeout_ = timeout; + } + + void Execute() + { + if (command_.get() == NULL) + { + // Cannot call Execute() twice + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + + const char* method; + + switch (method_) + { + case Orthanc::HttpMethod_Get: + method = "GET"; + break; + + case Orthanc::HttpMethod_Post: + method = "POST"; + break; + + case Orthanc::HttpMethod_Delete: + method = "DELETE"; + break; + + case Orthanc::HttpMethod_Put: + method = "PUT"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + strcpy(attr.requestMethod, method); + + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + attr.onsuccess = FetchContext::SuccessCallback; + attr.onerror = FetchContext::FailureCallback; + attr.timeoutMSecs = timeout_ * 1000; + + std::vector<const char*> headers; + headers.reserve(2 * headers_.size() + 1); + + std::string expectedContentType; + + for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) + { + std::string key; + Orthanc::Toolbox::ToLowerCase(key, it->first); + + if (key == "accept") + { + expectedContentType = it->second; + } + + if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header + { + headers.push_back(it->first.c_str()); + headers.push_back(it->second.c_str()); + } + } + + headers.push_back(NULL); // Termination of the array of HTTP headers + + attr.requestHeaders = &headers[0]; + + char* requestData = NULL; + if (!body_.empty()) + requestData = reinterpret_cast<char*>(malloc(body_.size())); + + try + { + if (!body_.empty()) + { + memcpy(requestData, &(body_[0]), body_.size()); + attr.requestDataSize = body_.size(); + attr.requestData = requestData; + } + attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); + + // Must be the last call to prevent memory leak on error + emscripten_fetch(&attr, uri_.c_str()); + } + catch(...) + { + if(requestData != NULL) + free(requestData); + throw; + } + } + }; + + void WebAssemblyOracle::Execute(const IObserver& receiver, + OrthancRestApiCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetMethod(command->GetMethod()); + fetch.SetUri(command->GetUri()); + fetch.SetHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + if (command->GetMethod() == Orthanc::HttpMethod_Post || + command->GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body; + command->SwapBody(body); + fetch.SetBody(body); + } + + fetch.Execute(); + } + + + void WebAssemblyOracle::Execute(const IObserver& receiver, + GetOrthancImageCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetUri(command->GetUri()); + fetch.SetHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + fetch.Execute(); + } + + + void WebAssemblyOracle::Execute(const IObserver& receiver, + GetOrthancWebViewerJpegCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetUri(command->GetUri()); + fetch.SetHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + fetch.Execute(); + } + + + + void WebAssemblyOracle::Schedule(const IObserver& receiver, + IOracleCommand* command) + { + std::auto_ptr<IOracleCommand> protection(command); + + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + switch (command->GetType()) + { + case IOracleCommand::Type_OrthancRestApi: + Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release())); + break; + + case IOracleCommand::Type_GetOrthancImage: + Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release())); + break; + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release())); + break; + + case IOracleCommand::Type_Sleep: + { + unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay(); + emscripten_set_timeout(TimeoutContext::Callback, timeoutMS, + new TimeoutContext(*this, receiver, protection.release())); + break; + } + + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " << command->GetType(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/WebAssemblyOracle.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,78 @@ +/** + * 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 + +#if !defined(ORTHANC_ENABLE_WASM) +# error The macro ORTHANC_ENABLE_WASM must be defined +#endif + +#if ORTHANC_ENABLE_WASM != 1 +# error This file can only compiled for WebAssembly +#endif + +#include "../Messages/IObservable.h" +#include "GetOrthancImageCommand.h" +#include "GetOrthancWebViewerJpegCommand.h" +#include "IOracle.h" +#include "OrthancRestApiCommand.h" + + +namespace OrthancStone +{ + class WebAssemblyOracle : + public IOracle, + public IObservable + { + private: + typedef std::map<std::string, std::string> HttpHeaders; + + class TimeoutContext; + class Emitter; + class FetchContext; + class FetchCommand; + + void Execute(const IObserver& receiver, + OrthancRestApiCommand* command); + + void Execute(const IObserver& receiver, + GetOrthancImageCommand* command); + + void Execute(const IObserver& receiver, + GetOrthancWebViewerJpegCommand* command); + + std::string orthancRoot_; + + public: + WebAssemblyOracle(MessageBroker& broker) : + IObservable(broker) + { + } + + void SetOrthancRoot(const std::string& root) + { + orthancRoot_ = root; + } + + virtual void Schedule(const IObserver& receiver, + IOracleCommand* command); + }; +}
--- a/Framework/Radiography/RadiographyDicomLayer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -22,7 +22,7 @@ #include "RadiographyDicomLayer.h" #include "RadiographyScene.h" -#include "../Toolbox/DicomFrameConverter.h" +#include "../Deprecated/Toolbox/DicomFrameConverter.h" #include <Core/OrthancException.h> #include <Core/Images/Image.h> @@ -85,11 +85,11 @@ { if (tmp == "MONOCHROME1") { - SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode_Monochrome1); + SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode_Monochrome1); } else if (tmp == "MONOCHROME2") { - SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode_Monochrome2); + SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode_Monochrome2); } } }
--- a/Framework/Radiography/RadiographyDicomLayer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,7 +21,7 @@ #pragma once -#include "../Toolbox/DicomFrameConverter.h" +#include "../Deprecated/Toolbox/DicomFrameConverter.h" #include "RadiographyLayer.h" #include <Plugins/Samples/Common/FullOrthancDataset.h>
--- a/Framework/Radiography/RadiographyLayer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyLayer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -125,7 +125,7 @@ hasSize_(false), width_(0), height_(0), - prefferedPhotometricDisplayMode_(PhotometricDisplayMode_Default), + prefferedPhotometricDisplayMode_(RadiographyPhotometricDisplayMode_Default), scene_(scene) { UpdateTransform(); @@ -137,7 +137,7 @@ UpdateTransform(); } - void RadiographyLayer::SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode prefferedPhotometricDisplayMode) + void RadiographyLayer::SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode prefferedPhotometricDisplayMode) { prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode; @@ -349,21 +349,21 @@ ControlPoint cp; switch (index) { - case ControlPoint_TopLeftCorner: - cp = ControlPoint(cropX, cropY, ControlPoint_TopLeftCorner); - break; + case RadiographyControlPointType_TopLeftCorner: + cp = ControlPoint(cropX, cropY, RadiographyControlPointType_TopLeftCorner); + break; - case ControlPoint_TopRightCorner: - cp = ControlPoint(cropX + cropWidth, cropY, ControlPoint_TopRightCorner); - break; + case RadiographyControlPointType_TopRightCorner: + cp = ControlPoint(cropX + cropWidth, cropY, RadiographyControlPointType_TopRightCorner); + break; - case ControlPoint_BottomLeftCorner: - cp = ControlPoint(cropX, cropY + cropHeight, ControlPoint_BottomLeftCorner); - break; + case RadiographyControlPointType_BottomLeftCorner: + cp = ControlPoint(cropX, cropY + cropHeight, RadiographyControlPointType_BottomLeftCorner); + break; - case ControlPoint_BottomRightCorner: - cp = ControlPoint(cropX + cropWidth, cropY + cropHeight, ControlPoint_BottomRightCorner); - break; + case RadiographyControlPointType_BottomRightCorner: + cp = ControlPoint(cropX + cropWidth, cropY + cropHeight, RadiographyControlPointType_BottomRightCorner); + break; default: throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
--- a/Framework/Radiography/RadiographyLayer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyLayer.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,7 +23,7 @@ #include "../Toolbox/AffineTransform2D.h" #include "../Toolbox/Extent2D.h" -#include "../Viewport/CairoContext.h" +#include "../Wrappers/CairoContext.h" #include "../Messages/IMessage.h" #include "../Messages/IObservable.h" @@ -31,6 +31,23 @@ { class RadiographyScene; + enum RadiographyControlPointType + { + RadiographyControlPointType_TopLeftCorner = 0, + RadiographyControlPointType_TopRightCorner = 1, + RadiographyControlPointType_BottomRightCorner = 2, + RadiographyControlPointType_BottomLeftCorner = 3 + }; + + enum RadiographyPhotometricDisplayMode + { + RadiographyPhotometricDisplayMode_Default, + + RadiographyPhotometricDisplayMode_Monochrome1, + RadiographyPhotometricDisplayMode_Monochrome2 + }; + + struct ControlPoint { double x; @@ -196,7 +213,7 @@ AffineTransform2D transform_; AffineTransform2D transformInverse_; Geometry geometry_; - PhotometricDisplayMode prefferedPhotometricDisplayMode_; + RadiographyPhotometricDisplayMode prefferedPhotometricDisplayMode_; const RadiographyScene& scene_; protected: @@ -210,7 +227,7 @@ return transformInverse_; } - void SetPreferredPhotomotricDisplayMode(PhotometricDisplayMode prefferedPhotometricDisplayMode); + void SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode prefferedPhotometricDisplayMode); private: void UpdateTransform(); @@ -325,7 +342,7 @@ virtual bool GetDefaultWindowing(float& center, float& width) const = 0; - PhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const + RadiographyPhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const { return prefferedPhotometricDisplayMode_; }
--- a/Framework/Radiography/RadiographyLayerCropTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerCropTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -112,8 +112,8 @@ { unsigned int targetX, targetWidth; - if (startControlPoint_.index == ControlPoint_TopLeftCorner || - startControlPoint_.index == ControlPoint_BottomLeftCorner) + if (startControlPoint_.index == RadiographyControlPointType_TopLeftCorner || + startControlPoint_.index == RadiographyControlPointType_BottomLeftCorner) { targetX = std::min(x, cropX_ + cropWidth_); targetWidth = cropX_ + cropWidth_ - targetX; @@ -126,8 +126,8 @@ unsigned int targetY, targetHeight; - if (startControlPoint_.index == ControlPoint_TopLeftCorner || - startControlPoint_.index == ControlPoint_TopRightCorner) + if (startControlPoint_.index == RadiographyControlPointType_TopLeftCorner || + startControlPoint_.index == RadiographyControlPointType_TopRightCorner) { targetY = std::min(y, cropY_ + cropHeight_); targetHeight = cropY_ + cropHeight_ - targetY;
--- a/Framework/Radiography/RadiographyLayerCropTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerCropTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,8 +22,8 @@ #pragma once #include "../Toolbox/UndoRedoStack.h" -#include "../Toolbox/ViewportGeometry.h" -#include "../Widgets/IWorldSceneMouseTracker.h" +#include "../Deprecated/Toolbox/ViewportGeometry.h" +#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h" #include "RadiographyScene.h" namespace OrthancStone
--- a/Framework/Radiography/RadiographyLayerMaskTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerMaskTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,8 +22,8 @@ #pragma once #include "../Toolbox/UndoRedoStack.h" -#include "../Toolbox/ViewportGeometry.h" -#include "../Widgets/IWorldSceneMouseTracker.h" +#include "../Deprecated/Toolbox/ViewportGeometry.h" +#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h" #include "RadiographyScene.h" namespace OrthancStone
--- a/Framework/Radiography/RadiographyLayerMoveTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerMoveTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Toolbox/UndoRedoStack.h" -#include "../Widgets/IWorldSceneMouseTracker.h" +#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h" #include "RadiographyScene.h" namespace OrthancStone
--- a/Framework/Radiography/RadiographyLayerResizeTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerResizeTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -102,20 +102,20 @@ size_t oppositeControlPointType; switch (startControlPoint.index) { - case ControlPoint_TopLeftCorner: - oppositeControlPointType = ControlPoint_BottomRightCorner; + case RadiographyControlPointType_TopLeftCorner: + oppositeControlPointType = RadiographyControlPointType_BottomRightCorner; break; - case ControlPoint_TopRightCorner: - oppositeControlPointType = ControlPoint_BottomLeftCorner; + case RadiographyControlPointType_TopRightCorner: + oppositeControlPointType = RadiographyControlPointType_BottomLeftCorner; break; - case ControlPoint_BottomLeftCorner: - oppositeControlPointType = ControlPoint_TopRightCorner; + case RadiographyControlPointType_BottomLeftCorner: + oppositeControlPointType = RadiographyControlPointType_TopRightCorner; break; - case ControlPoint_BottomRightCorner: - oppositeControlPointType = ControlPoint_TopLeftCorner; + case RadiographyControlPointType_BottomRightCorner: + oppositeControlPointType = RadiographyControlPointType_TopLeftCorner; break; default:
--- a/Framework/Radiography/RadiographyLayerResizeTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerResizeTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Toolbox/UndoRedoStack.h" -#include "../Widgets/IWorldSceneMouseTracker.h" +#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h" #include "RadiographyScene.h" namespace OrthancStone
--- a/Framework/Radiography/RadiographyLayerRotateTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyLayerRotateTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,8 +22,8 @@ #pragma once #include "../Toolbox/UndoRedoStack.h" -#include "../Toolbox/ViewportGeometry.h" -#include "../Widgets/IWorldSceneMouseTracker.h" +#include "../Deprecated/Toolbox/ViewportGeometry.h" +#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h" #include "RadiographyScene.h"
--- a/Framework/Radiography/RadiographyScene.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -25,7 +25,7 @@ #include "RadiographyDicomLayer.h" #include "RadiographyTextLayer.h" #include "RadiographyMaskLayer.h" -#include "../Toolbox/DicomFrameConverter.h" +#include "../Deprecated/Toolbox/DicomFrameConverter.h" #include <Core/Images/Image.h> #include <Core/Images/ImageProcessing.h> @@ -172,18 +172,18 @@ } } - PhotometricDisplayMode RadiographyScene::GetPreferredPhotomotricDisplayMode() const + RadiographyPhotometricDisplayMode RadiographyScene::GetPreferredPhotomotricDisplayMode() const { // return the mode of the first layer who "cares" about its display mode (normaly, the one and only layer that is a DicomLayer) for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++) { - if (it->second->GetPreferredPhotomotricDisplayMode() != PhotometricDisplayMode_Default) + if (it->second->GetPreferredPhotomotricDisplayMode() != RadiographyPhotometricDisplayMode_Default) { return it->second->GetPreferredPhotomotricDisplayMode(); } } - return PhotometricDisplayMode_Default; + return RadiographyPhotometricDisplayMode_Default; } @@ -345,7 +345,7 @@ const std::string& instance, unsigned int frame, Deprecated::DicomFrameConverter* converter, // takes ownership - PhotometricDisplayMode preferredPhotometricDisplayMode, + RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode, RadiographyLayer::Geometry* geometry) { RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this))); @@ -642,9 +642,9 @@ createDicomRequestContent["Tags"] = dicomTags; - PhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode(); - if ((invert && photometricMode != PhotometricDisplayMode_Monochrome2) || - (!invert && photometricMode == PhotometricDisplayMode_Monochrome1)) + RadiographyPhotometricDisplayMode photometricMode = GetPreferredPhotomotricDisplayMode(); + if ((invert && photometricMode != RadiographyPhotometricDisplayMode_Monochrome2) || + (!invert && photometricMode == RadiographyPhotometricDisplayMode_Monochrome1)) { createDicomRequestContent["Tags"]["PhotometricInterpretation"] = "MONOCHROME1"; }
--- a/Framework/Radiography/RadiographyScene.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,9 +22,9 @@ #pragma once #include "RadiographyLayer.h" -#include "../Toolbox/DicomFrameConverter.h" -#include "../Toolbox/OrthancApiClient.h" -#include "Framework/StoneEnumerations.h" +#include "../Deprecated/Toolbox/DicomFrameConverter.h" +#include "../Deprecated/Toolbox/OrthancApiClient.h" +#include "../StoneEnumerations.h" #include "Core/Images/Image.h" #include "Core/Images/ImageProcessing.h" @@ -172,7 +172,7 @@ virtual void SetWindowing(float center, float width); - PhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const; + RadiographyPhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const; RadiographyLayer& LoadText(const Orthanc::Font& font, const std::string& utf8, @@ -194,7 +194,7 @@ const std::string& instance, unsigned int frame, Deprecated::DicomFrameConverter* converter, // takes ownership - PhotometricDisplayMode preferredPhotometricDisplayMode, + RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode, RadiographyLayer::Geometry* geometry); virtual RadiographyLayer& LoadDicomFrame(Deprecated::OrthancApiClient& orthanc,
--- a/Framework/Radiography/RadiographySceneReader.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographySceneReader.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -21,7 +21,7 @@ #include "RadiographySceneReader.h" -#include <Framework/Toolbox/DicomFrameConverter.h> +#include "../Deprecated/Toolbox/DicomFrameConverter.h" #include <Core/Images/FontRegistry.h> #include <Core/Images/PngReader.h> @@ -33,7 +33,7 @@ void RadiographySceneBuilder::Read(const Json::Value& input, Orthanc::ImageAccessor* dicomImage /* takes ownership */, Deprecated::DicomFrameConverter* dicomFrameConverter /* takes ownership */, - PhotometricDisplayMode preferredPhotometricDisplayMode + RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode ) { dicomImage_.reset(dicomImage);
--- a/Framework/Radiography/RadiographySceneReader.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographySceneReader.h Mon Jun 24 14:35:00 2019 +0200 @@ -26,7 +26,7 @@ #include "RadiographyDicomLayer.h" #include "RadiographyMaskLayer.h" #include "RadiographyTextLayer.h" -#include "../Toolbox/OrthancApiClient.h" +#include "../Deprecated/Toolbox/OrthancApiClient.h" #include <json/value.h> #include <Core/Images/FontRegistry.h> @@ -42,8 +42,8 @@ RadiographyScene& scene_; const Orthanc::FontRegistry* fontRegistry_; std::auto_ptr<Orthanc::ImageAccessor> dicomImage_; - std::auto_ptr<Deprecated::DicomFrameConverter> dicomFrameConverter_; - PhotometricDisplayMode preferredPhotometricDisplayMode_; + std::auto_ptr<Deprecated::DicomFrameConverter> dicomFrameConverter_; + RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode_; public: RadiographySceneBuilder(RadiographyScene& scene) : @@ -56,7 +56,7 @@ void Read(const Json::Value& input, Orthanc::ImageAccessor* dicomImage, // takes ownership Deprecated::DicomFrameConverter* dicomFrameConverter, // takes ownership - PhotometricDisplayMode preferredPhotometricDisplayMode + RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode ); void SetFontRegistry(const Orthanc::FontRegistry& fontRegistry)
--- a/Framework/Radiography/RadiographyWidget.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyWidget.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -35,7 +35,7 @@ // MONOCHROME1 images must be inverted and the user can invert the // image, too -> XOR the two return (scene_->GetPreferredPhotomotricDisplayMode() == - PhotometricDisplayMode_Monochrome1) ^ invert_; + RadiographyPhotometricDisplayMode_Monochrome1) ^ invert_; } void RadiographyWidget::RenderBackground( @@ -46,14 +46,14 @@ switch (scene_->GetPreferredPhotomotricDisplayMode()) { - case PhotometricDisplayMode_Monochrome1: - case PhotometricDisplayMode_Default: + case RadiographyPhotometricDisplayMode_Monochrome1: + case RadiographyPhotometricDisplayMode_Default: if (IsInvertedInternal()) backgroundValue = maxValue; else backgroundValue = minValue; break; - case PhotometricDisplayMode_Monochrome2: + case RadiographyPhotometricDisplayMode_Monochrome2: if (IsInvertedInternal()) backgroundValue = minValue; else
--- a/Framework/Radiography/RadiographyWidget.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyWidget.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,7 +21,7 @@ #pragma once -#include "../Widgets/WorldSceneWidget.h" +#include "../Deprecated/Widgets/WorldSceneWidget.h" #include "RadiographyScene.h"
--- a/Framework/Radiography/RadiographyWindowingTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Radiography/RadiographyWindowingTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Toolbox/UndoRedoStack.h" -#include "../Widgets/IWorldSceneMouseTracker.h" +#include "../Deprecated/Widgets/IWorldSceneMouseTracker.h" #include "RadiographyScene.h" namespace OrthancStone
--- a/Framework/Scene2D/CairoCompositor.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/CairoCompositor.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -24,6 +24,7 @@ #include "Internals/CairoColorTextureRenderer.h" #include "Internals/CairoFloatTextureRenderer.h" #include "Internals/CairoInfoPanelRenderer.h" +#include "Internals/CairoLookupTableTextureRenderer.h" #include "Internals/CairoPolylineRenderer.h" #include "Internals/CairoTextRenderer.h" @@ -60,6 +61,9 @@ case ISceneLayer::Type_FloatTexture: return new Internals::CairoFloatTextureRenderer(*this, layer); + case ISceneLayer::Type_LookupTableTexture: + return new Internals::CairoLookupTableTextureRenderer(*this, layer); + case ISceneLayer::Type_Text: { const TextSceneLayer& l = dynamic_cast<const TextSceneLayer&>(layer);
--- a/Framework/Scene2D/CairoCompositor.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/CairoCompositor.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Fonts/GlyphBitmapAlphabet.h" -#include "../Viewport/CairoContext.h" +#include "../Wrappers/CairoContext.h" #include "Internals/CompositorHelper.h" #include "Internals/ICairoContextProvider.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Color.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,82 @@ +/** + * 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 <stdint.h> + +namespace OrthancStone +{ + class Color + { + private: + uint8_t red_; + uint8_t green_; + uint8_t blue_; + + public: + Color() : + red_(255), + green_(255), + blue_(255) + { + } + + Color(uint8_t red, + uint8_t green, + uint8_t blue) : + red_(red), + green_(green), + blue_(blue) + { + } + + uint8_t GetRed() const + { + return red_; + } + + uint8_t GetGreen() const + { + return green_; + } + + uint8_t GetBlue() const + { + return blue_; + } + + float GetRedAsFloat() const + { + return static_cast<float>(red_) / 255.0f; + } + + float GetGreenAsFloat() const + { + return static_cast<float>(green_) / 255.0f; + } + + float GetBlueAsFloat() const + { + return static_cast<float>(blue_) / 255.0f; + } + }; +}
--- a/Framework/Scene2D/ColorSceneLayer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/ColorSceneLayer.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,18 +22,17 @@ #pragma once #include "ISceneLayer.h" -#include <Core/Enumerations.h> +#include "Color.h" -#include <stdint.h> +#include <Core/Enumerations.h> // For ORTHANC_OVERRIDE namespace OrthancStone { + // TODO - Is this needed? class ColorSceneLayer : public ISceneLayer { private: - uint8_t red_; - uint8_t green_; - uint8_t blue_; + Color color_; uint64_t revision_; protected: @@ -45,9 +44,6 @@ public: ColorSceneLayer() : - red_(255), - green_(255), - blue_(255), revision_(0) { } @@ -61,40 +57,19 @@ uint8_t green, uint8_t blue) { - red_ = red; - green_ = green; - blue_ = blue; + color_ = Color(red, green, blue); BumpRevision(); } - uint8_t GetRed() const + void SetColor(const Color& color) { - return red_; - } - - uint8_t GetGreen() const - { - return green_; + color_ = color; + BumpRevision(); } - uint8_t GetBlue() const - { - return blue_; - } - - float GetRedAsFloat() const + const Color& GetColor() const { - return static_cast<float>(red_) / 255.0f; - } - - float GetGreenAsFloat() const - { - return static_cast<float>(green_) / 255.0f; - } - - float GetBlueAsFloat() const - { - return static_cast<float>(blue_) / 255.0f; + return color_; } }; }
--- a/Framework/Scene2D/ColorTextureSceneLayer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/ColorTextureSceneLayer.h Mon Jun 24 14:35:00 2019 +0200 @@ -28,6 +28,7 @@ class ColorTextureSceneLayer : public TextureBaseSceneLayer { public: + // If using RGBA32, premultiplied alpha is assumed ColorTextureSceneLayer(const Orthanc::ImageAccessor& texture); virtual ISceneLayer* Clone() const;
--- a/Framework/Scene2D/FloatTextureSceneLayer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/FloatTextureSceneLayer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -27,7 +27,8 @@ namespace OrthancStone { - FloatTextureSceneLayer::FloatTextureSceneLayer(const Orthanc::ImageAccessor& texture) + FloatTextureSceneLayer::FloatTextureSceneLayer(const Orthanc::ImageAccessor& texture) : + inverted_(false) { { std::auto_ptr<Orthanc::ImageAccessor> t( @@ -86,6 +87,13 @@ } + void FloatTextureSceneLayer::SetInverted(bool inverted) + { + inverted_ = inverted; + IncrementRevision(); + } + + void FloatTextureSceneLayer::FitRange() { float minValue, maxValue; @@ -116,6 +124,7 @@ cloned->windowing_ = windowing_; cloned->customCenter_ = customCenter_; cloned->customWidth_ = customWidth_; + cloned->inverted_ = inverted_; return cloned.release(); }
--- a/Framework/Scene2D/FloatTextureSceneLayer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/FloatTextureSceneLayer.h Mon Jun 24 14:35:00 2019 +0200 @@ -31,9 +31,10 @@ ImageWindowing windowing_; float customCenter_; float customWidth_; + bool inverted_; public: - // The pixel format must be "Float32" + // The pixel format must be convertible to "Float32" FloatTextureSceneLayer(const Orthanc::ImageAccessor& texture); void SetWindowing(ImageWindowing windowing); @@ -49,6 +50,14 @@ return windowing_; } + // To achieve MONOCHROME1 photometric interpretation + void SetInverted(bool inverted); + + bool IsInverted() const + { + return inverted_; + } + void FitRange(); virtual ISceneLayer* Clone() const;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/GrayscaleStyleConfigurator.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,81 @@ +/** + * 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 "GrayscaleStyleConfigurator.h" + +#include "FloatTextureSceneLayer.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + void GrayscaleStyleConfigurator::SetWindowing(ImageWindowing windowing) + { + hasWindowing_ = true; + windowing_ = windowing; + revision_++; + } + + + void GrayscaleStyleConfigurator::SetLinearInterpolation(bool enabled) + { + linearInterpolation_ = enabled; + revision_++; + } + + + TextureBaseSceneLayer* GrayscaleStyleConfigurator::CreateTextureFromImage( + const Orthanc::ImageAccessor& image) const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + TextureBaseSceneLayer* GrayscaleStyleConfigurator::CreateTextureFromDicom( + const Orthanc::ImageAccessor& frame, + const DicomInstanceParameters& parameters) const + { + std::auto_ptr<TextureBaseSceneLayer> layer(parameters.CreateTexture(frame)); + + if (layer.get() == NULL || + layer->GetTexture().GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + else + { + return layer.release(); + } + } + + + void GrayscaleStyleConfigurator::ApplyStyle(ISceneLayer& layer) const + { + FloatTextureSceneLayer& l = dynamic_cast<FloatTextureSceneLayer&>(layer); + + l.SetLinearInterpolation(linearInterpolation_); + + if (hasWindowing_) + { + l.SetWindowing(windowing_); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/GrayscaleStyleConfigurator.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,73 @@ +/** + * 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 "ILayerStyleConfigurator.h" + +namespace OrthancStone +{ + /** + Creates layers to display the supplied image in grayscale. No dynamic + style is available. + */ + class GrayscaleStyleConfigurator : public ILayerStyleConfigurator + { + private: + uint64_t revision_; + bool linearInterpolation_; + bool hasWindowing_; + ImageWindowing windowing_; + + // TODO - Add custom windowing + + public: + GrayscaleStyleConfigurator() : + revision_(0), + linearInterpolation_(false), + hasWindowing_(false) + { + } + + void SetWindowing(ImageWindowing windowing); + + void SetLinearInterpolation(bool enabled); + + bool IsLinearInterpolation() const + { + return linearInterpolation_; + } + + virtual uint64_t GetRevision() const + { + return revision_; + } + + virtual TextureBaseSceneLayer* CreateTextureFromImage( + const Orthanc::ImageAccessor& image) const; + + virtual TextureBaseSceneLayer* CreateTextureFromDicom( + const Orthanc::ImageAccessor& frame, + const DicomInstanceParameters& parameters) const; + + virtual void ApplyStyle(ISceneLayer& layer) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/ILayerStyleConfigurator.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,52 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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/DicomInstanceParameters.h" + +namespace OrthancStone +{ + /** + This interface is implemented by objects able to create an ISceneLayer + suitable to display the Orthanc image supplied to the CreateTextureXX + factory methods (taking Dicom parameters into account if relevant). + + It can also refresh the style of an existing layer afterwards, to match + the configurator settings. + */ + class ILayerStyleConfigurator + { + public: + virtual ~ILayerStyleConfigurator() + { + } + + virtual uint64_t GetRevision() const = 0; + + virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const = 0; + + virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame, + const DicomInstanceParameters& parameters) const = 0; + + virtual void ApplyStyle(ISceneLayer& layer) const = 0; + }; +}
--- a/Framework/Scene2D/ISceneLayer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/ISceneLayer.h Mon Jun 24 14:35:00 2019 +0200 @@ -37,7 +37,8 @@ Type_ColorTexture, Type_Polyline, Type_Text, - Type_FloatTexture + Type_FloatTexture, + Type_LookupTableTexture }; virtual ~ISceneLayer()
--- a/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -44,13 +44,17 @@ isLinearInterpolation_ = l.IsLinearInterpolation(); } - - void CairoColorTextureRenderer::Render(const AffineTransform2D& transform) + + void CairoColorTextureRenderer::RenderColorTexture(ICairoContextProvider& target, + const AffineTransform2D& transform, + CairoSurface& texture, + const AffineTransform2D& textureTransform, + bool isLinearInterpolation) { - cairo_t* cr = target_.GetCairoContext(); + cairo_t* cr = target.GetCairoContext(); AffineTransform2D t = - AffineTransform2D::Combine(transform, textureTransform_); + AffineTransform2D::Combine(transform, textureTransform); Matrix h = t.GetHomogeneousMatrix(); cairo_save(cr); @@ -60,9 +64,9 @@ cairo_transform(cr, &m); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - cairo_set_source_surface(cr, texture_.GetObject(), 0, 0); + cairo_set_source_surface(cr, texture.GetObject(), 0, 0); - if (isLinearInterpolation_) + if (isLinearInterpolation) { cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR); }
--- a/Framework/Scene2D/Internals/CairoColorTextureRenderer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/CairoColorTextureRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,7 +21,7 @@ #pragma once -#include "../../Viewport/CairoSurface.h" +#include "../../Wrappers/CairoSurface.h" #include "CompositorHelper.h" #include "ICairoContextProvider.h" @@ -43,7 +43,17 @@ virtual void Update(const ISceneLayer& layer); - virtual void Render(const AffineTransform2D& transform); + virtual void Render(const AffineTransform2D& transform) + { + RenderColorTexture(target_, transform, texture_, + textureTransform_, isLinearInterpolation_); + } + + static void RenderColorTexture(ICairoContextProvider& target, + const AffineTransform2D& transform, + CairoSurface& texture, + const AffineTransform2D& textureTransform, + bool isLinearInterpolation); }; } }
--- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -21,6 +21,7 @@ #include "CairoFloatTextureRenderer.h" +#include "CairoColorTextureRenderer.h" #include "../FloatTextureSceneLayer.h" namespace OrthancStone @@ -71,6 +72,11 @@ uint8_t vv = static_cast<uint8_t>(v); + if (l.IsInverted()) + { + vv = 255 - vv; + } + q[0] = vv; q[1] = vv; q[2] = vv; @@ -84,33 +90,8 @@ void CairoFloatTextureRenderer::Render(const AffineTransform2D& transform) { - cairo_t* cr = target_.GetCairoContext(); - - AffineTransform2D t = - AffineTransform2D::Combine(transform, textureTransform_); - Matrix h = t.GetHomogeneousMatrix(); - - cairo_save(cr); - - cairo_matrix_t m; - cairo_matrix_init(&m, h(0, 0), h(1, 0), h(0, 1), h(1, 1), h(0, 2), h(1, 2)); - cairo_transform(cr, &m); - - cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - cairo_set_source_surface(cr, texture_.GetObject(), 0, 0); - - if (isLinearInterpolation_) - { - cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR); - } - else - { - cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); - } - - cairo_paint(cr); - - cairo_restore(cr); + CairoColorTextureRenderer::RenderColorTexture(target_, transform, texture_, + textureTransform_, isLinearInterpolation_); } } }
--- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,7 +21,7 @@ #pragma once -#include "../../Viewport/CairoSurface.h" +#include "../../Wrappers/CairoSurface.h" #include "CompositorHelper.h" #include "ICairoContextProvider.h"
--- a/Framework/Scene2D/Internals/CairoInfoPanelRenderer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/CairoInfoPanelRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,7 +21,7 @@ #pragma once -#include "../../Viewport/CairoSurface.h" +#include "../../Wrappers/CairoSurface.h" #include "CompositorHelper.h" #include "ICairoContextProvider.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,108 @@ +/** + * 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 "CairoLookupTableTextureRenderer.h" + +#include "CairoColorTextureRenderer.h" +#include "../LookupTableTextureSceneLayer.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + namespace Internals + { + void CairoLookupTableTextureRenderer::Update(const ISceneLayer& layer) + { + const LookupTableTextureSceneLayer& l = dynamic_cast<const LookupTableTextureSceneLayer&>(layer); + + textureTransform_ = l.GetTransform(); + isLinearInterpolation_ = l.IsLinearInterpolation(); + + const float a = l.GetMinValue(); + float slope; + + if (l.GetMinValue() >= l.GetMaxValue()) + { + slope = 0; + } + else + { + slope = 256.0f / (l.GetMaxValue() - l.GetMinValue()); + } + + const Orthanc::ImageAccessor& source = l.GetTexture(); + const unsigned int width = source.GetWidth(); + const unsigned int height = source.GetHeight(); + texture_.SetSize(width, height, true /* alpha channel is enabled */); + + Orthanc::ImageAccessor target; + texture_.GetWriteableAccessor(target); + + const std::vector<uint8_t>& lut = l.GetLookupTable(); + if (lut.size() != 4 * 256) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + assert(source.GetFormat() == Orthanc::PixelFormat_Float32 && + target.GetFormat() == Orthanc::PixelFormat_BGRA32 && + sizeof(float) == 4); + + for (unsigned int y = 0; y < height; y++) + { + const float* p = reinterpret_cast<const float*>(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + float v = (*p - a) * slope; + if (v <= 0) + { + v = 0; + } + else if (v >= 255) + { + v = 255; + } + + uint8_t vv = static_cast<uint8_t>(v); + + q[0] = lut[4 * vv + 2]; // B + q[1] = lut[4 * vv + 1]; // G + q[2] = lut[4 * vv + 0]; // R + q[3] = lut[4 * vv + 3]; // A + + p++; + q += 4; + } + } + + cairo_surface_mark_dirty(texture_.GetObject()); + } + + void CairoLookupTableTextureRenderer::Render(const AffineTransform2D& transform) + { + CairoColorTextureRenderer::RenderColorTexture(target_, transform, texture_, + textureTransform_, isLinearInterpolation_); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,53 @@ +/** + * 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 "../../Wrappers/CairoSurface.h" +#include "CompositorHelper.h" +#include "ICairoContextProvider.h" + +namespace OrthancStone +{ + namespace Internals + { + class CairoLookupTableTextureRenderer : public CompositorHelper::ILayerRenderer + { + private: + ICairoContextProvider& target_; + CairoSurface texture_; + AffineTransform2D textureTransform_; + bool isLinearInterpolation_; + + public: + CairoLookupTableTextureRenderer(ICairoContextProvider& target, + const ISceneLayer& layer) : + target_(target) + { + Update(layer); + } + + virtual void Update(const ISceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + }; + } +}
--- a/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -33,11 +33,15 @@ cairo_t* cr = GetCairoContext(); - cairo_set_source_rgb(cr, layer.GetRedAsFloat(), layer.GetGreenAsFloat(), layer.GetBlueAsFloat()); cairo_set_line_width(cr, layer.GetThickness()); for (size_t i = 0; i < layer.GetChainsCount(); i++) { + const Color& color = layer.GetColor(i); + cairo_set_source_rgb(cr, color.GetRedAsFloat(), + color.GetGreenAsFloat(), + color.GetBlueAsFloat()); + const PolylineSceneLayer::Chain& chain = layer.GetChain(i); if (!chain.empty()) @@ -62,9 +66,9 @@ cairo_line_to(cr, p.GetX(), p.GetY()); } } + + cairo_stroke(cr); } - - cairo_stroke(cr); } } }
--- a/Framework/Scene2D/Internals/CairoTextRenderer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/CairoTextRenderer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -48,9 +48,7 @@ } const unsigned int width = source->GetWidth(); - const unsigned int red = layer.GetRed(); - const unsigned int green = layer.GetGreen(); - const unsigned int blue = layer.GetBlue(); + const Color& color = layer.GetColor(); for (unsigned int y = 0; y < source->GetHeight(); y++) { @@ -62,9 +60,9 @@ unsigned int alpha = *p; // Premultiplied alpha - q[0] = static_cast<uint8_t>((blue * alpha) / 255); - q[1] = static_cast<uint8_t>((green * alpha) / 255); - q[2] = static_cast<uint8_t>((red * alpha) / 255); + q[0] = static_cast<uint8_t>((color.GetBlue() * alpha) / 255); + q[1] = static_cast<uint8_t>((color.GetGreen() * alpha) / 255); + q[2] = static_cast<uint8_t>((color.GetRed() * alpha) / 255); q[3] = *p; p++; @@ -85,7 +83,9 @@ const TextSceneLayer& layer = GetLayer<TextSceneLayer>(); cairo_t* cr = GetCairoContext(); - cairo_set_source_rgb(cr, layer.GetRedAsFloat(), layer.GetGreenAsFloat(), layer.GetBlueAsFloat()); + cairo_set_source_rgb(cr, layer.GetColor().GetRedAsFloat(), + layer.GetColor().GetGreenAsFloat(), + layer.GetColor().GetBlueAsFloat()); double dx, dy; // In pixels ComputeAnchorTranslation(dx, dy, layer.GetAnchor(), text_.GetWidth(),
--- a/Framework/Scene2D/Internals/CairoTextRenderer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/CairoTextRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../../Fonts/GlyphBitmapAlphabet.h" -#include "../../Viewport/CairoSurface.h" +#include "../../Wrappers/CairoSurface.h" #include "../TextSceneLayer.h" #include "CairoBaseRenderer.h"
--- a/Framework/Scene2D/Internals/CompositorHelper.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/CompositorHelper.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,6 +23,10 @@ #include "../Scene2D.h" +#include <boost/noncopyable.hpp> + +#include <map> + namespace OrthancStone { namespace Internals
--- a/Framework/Scene2D/Internals/FixedPointAligner.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/FixedPointAligner.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -18,26 +18,26 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ -#include <Framework/Scene2DViewport/ViewportController.h> +#include "../../Scene2DViewport/ViewportController.h" #include "FixedPointAligner.h" namespace OrthancStone { namespace Internals { - FixedPointAligner::FixedPointAligner(ViewportControllerWPtr controllerW, + FixedPointAligner::FixedPointAligner(boost::weak_ptr<ViewportController> controllerW, const ScenePoint2D& p) : controllerW_(controllerW) , canvas_(p) { - ViewportControllerPtr controller = controllerW_.lock(); + boost::shared_ptr<ViewportController> controller = controllerW_.lock(); pivot_ = canvas_.Apply(controller->GetCanvasToSceneTransform()); } void FixedPointAligner::Apply() { - ViewportControllerPtr controller = controllerW_.lock(); + boost::shared_ptr<ViewportController> controller = controllerW_.lock(); ScenePoint2D p = canvas_.Apply(controller->GetCanvasToSceneTransform()); controller->SetSceneToCanvasTransform(
--- a/Framework/Scene2D/Internals/FixedPointAligner.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/FixedPointAligner.h Mon Jun 24 14:35:00 2019 +0200 @@ -20,24 +20,24 @@ #pragma once -#include <Framework/Scene2DViewport/PointerTypes.h> -#include <Framework/Scene2D/ScenePoint2D.h> +#include "../../Scene2DViewport/PredeclaredTypes.h" +#include "../../Scene2D/ScenePoint2D.h" namespace OrthancStone { namespace Internals { // During a mouse event that modifies the view of a scene, keeps - // one point (the pivot) at the same position on the canvas + // one point (the pivot) at a fixed position on the canvas class FixedPointAligner : public boost::noncopyable { private: - ViewportControllerWPtr controllerW_; + boost::weak_ptr<ViewportController> controllerW_; ScenePoint2D pivot_; ScenePoint2D canvas_; public: - FixedPointAligner(ViewportControllerWPtr controllerW, + FixedPointAligner(boost::weak_ptr<ViewportController> controllerW, const ScenePoint2D& p); void Apply();
--- a/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -28,7 +28,7 @@ namespace Internals { OpenGLBasicPolylineRenderer::OpenGLBasicPolylineRenderer(OpenGL::IOpenGLContext& context, - const PolylineSceneLayer& layer) : + const PolylineSceneLayer& layer) : context_(context) { layer_.Copy(layer); @@ -42,12 +42,14 @@ transform); glUseProgram(0); - glColor3ub(layer_.GetRed(), layer_.GetGreen(), layer_.GetBlue()); glBegin(GL_LINES); for (size_t i = 0; i < layer_.GetChainsCount(); i++) { + const Color& color = layer_.GetColor(i); + glColor3ub(color.GetRed(), color.GetGreen(), color.GetBlue()); + const PolylineSceneLayer::Chain& chain = layer_.GetChain(i); if (chain.size() > 1)
--- a/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -29,28 +29,28 @@ static const char* FRAGMENT_SHADER = ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE - "uniform float u_offset; \n" - "uniform float u_slope; \n" - "uniform float u_windowCenter; \n" - "uniform float u_windowWidth; \n" - "uniform sampler2D u_texture; \n" - "varying vec2 v_texcoord; \n" - "void main() \n" - "{ \n" - " vec4 t = texture2D(u_texture, v_texcoord); \n" - " float v = (t.r * 256.0 + t.g) * 256.0; \n" - " v = v * u_slope + u_offset; \n" // (*) - " float a = u_windowCenter - u_windowWidth; \n" - " float dy = 1.0 / (2.0 * u_windowWidth); \n" - " if (v <= a) \n" - " v = 0.0; \n" - " else \n" - " { \n" - " v = (v - a) * dy; \n" - " if (v >= 1.0) \n" - " v = 1.0; \n" - " } \n" - " gl_FragColor = vec4(v, v, v, 1); \n" + "uniform float u_offset; \n" + "uniform float u_slope; \n" + "uniform float u_windowCenter; \n" + "uniform float u_windowWidth; \n" + "uniform sampler2D u_texture; \n" + "varying vec2 v_texcoord; \n" + "void main() \n" + "{ \n" + " vec4 t = texture2D(u_texture, v_texcoord); \n" + " float v = (t.r * 256.0 + t.g) * 256.0; \n" + " v = v * u_slope + u_offset; \n" // (*) + " float a = u_windowCenter - u_windowWidth / 2.0; \n" + " float dy = 1.0 / u_windowWidth; \n" + " if (v <= a) \n" + " v = 0.0; \n" + " else \n" + " { \n" + " v = (v - a) * dy; \n" + " if (v >= 1.0) \n" + " v = 1.0; \n" + " } \n" + " gl_FragColor = vec4(v, v, v, 1); \n" "}";
--- a/Framework/Scene2D/Internals/OpenGLLinesProgram.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/OpenGLLinesProgram.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -26,6 +26,7 @@ static const unsigned int COMPONENTS_POSITION = 3; +static const unsigned int COMPONENTS_COLOR = 3; static const unsigned int COMPONENTS_MITER = 2; @@ -33,12 +34,15 @@ ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE "attribute vec2 a_miter_direction; \n" "attribute vec4 a_position; \n" + "attribute vec3 a_color; \n" "uniform float u_thickness; \n" "uniform mat4 u_matrix; \n" "varying float v_distance; \n" + "varying vec3 v_color; \n" "void main() \n" "{ \n" " v_distance = a_position.z; \n" + " v_color = a_color; \n" " gl_Position = u_matrix * vec4(a_position.xy + a_position.z * a_miter_direction * u_thickness, 0, 1); \n" "}"; @@ -47,20 +51,20 @@ ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE "uniform bool u_antialiasing; \n" "uniform float u_antialiasing_start; \n" - "uniform vec3 u_color; \n" "varying float v_distance; \n" // Distance of the point to the segment + "varying vec3 v_color; \n" "void main() \n" "{ \n" " float d = abs(v_distance); \n" " if (!u_antialiasing || \n" " d <= u_antialiasing_start) \n" - " gl_FragColor = vec4(u_color, 1); \n" + " gl_FragColor = vec4(v_color, 1); \n" " else if (d >= 1.0) \n" " gl_FragColor = vec4(0, 0, 0, 0); \n" " else \n" " { \n" " float alpha = 1.0 - smoothstep(u_antialiasing_start, 1.0, d); \n" - " gl_FragColor = vec4(u_color * alpha, alpha); \n" + " gl_FragColor = vec4(v_color * alpha, alpha); \n" " } \n" "}"; @@ -197,7 +201,9 @@ } void AddTriangles(std::vector<float>& coords, - std::vector<float>& miterDirections) + std::vector<float>& miterDirections, + std::vector<float>& colors, + const Color& color) { if (isEmpty_) { @@ -239,6 +245,14 @@ miterDirections.push_back(static_cast<float>(miterY1_)); miterDirections.push_back(static_cast<float>(miterX2_)); miterDirections.push_back(static_cast<float>(miterY2_)); + + // Add the colors of the 2 triangles (leading to 2 * 3 values) + for (unsigned int i = 0; i < 6; i++) + { + colors.push_back(color.GetRedAsFloat()); + colors.push_back(color.GetGreenAsFloat()); + colors.push_back(color.GetBlueAsFloat()); + } } }; @@ -247,10 +261,7 @@ const PolylineSceneLayer& layer) : context_(context), verticesCount_(0), - thickness_(static_cast<float>(layer.GetThickness())), - red_(layer.GetRedAsFloat()), - green_(layer.GetGreenAsFloat()), - blue_(layer.GetBlueAsFloat()) + thickness_(static_cast<float>(layer.GetThickness())) { // High-level reference: // https://mattdesl.svbtle.com/drawing-lines-is-hard @@ -271,8 +282,9 @@ countVertices += countSegments * 2 * 3; } - std::vector<float> coords, miterDirections; + std::vector<float> coords, colors, miterDirections; coords.reserve(countVertices * COMPONENTS_POSITION); + colors.reserve(countVertices * COMPONENTS_COLOR); miterDirections.reserve(countVertices * COMPONENTS_MITER); for (size_t i = 0; i < layer.GetChainsCount(); i++) @@ -307,24 +319,29 @@ { if (!segments[j].IsEmpty()) { - segments[j].AddTriangles(coords, miterDirections); + segments[j].AddTriangles(coords, miterDirections, colors, layer.GetColor(i)); } } } } + assert(coords.size() == colors.size()); + if (!coords.empty()) { verticesCount_ = coords.size() / COMPONENTS_POSITION; context_.MakeCurrent(); - glGenBuffers(2, buffers_); + glGenBuffers(3, buffers_); glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * coords.size(), &coords[0], GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * miterDirections.size(), &miterDirections[0], GL_STATIC_DRAW); + + glBindBuffer(GL_ARRAY_BUFFER, buffers_[2]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * colors.size(), &colors[0], GL_STATIC_DRAW); } } @@ -334,7 +351,7 @@ if (!IsEmpty()) { context_.MakeCurrent(); - glDeleteBuffers(2, buffers_); + glDeleteBuffers(3, buffers_); } } @@ -365,10 +382,22 @@ } + GLuint OpenGLLinesProgram::Data::GetColorsBuffer() const + { + if (IsEmpty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return buffers_[2]; + } + } + + OpenGLLinesProgram::OpenGLLinesProgram(OpenGL::IOpenGLContext& context) : context_(context) { - context_.MakeCurrent(); program_.reset(new OpenGL::OpenGLProgram); @@ -388,13 +417,12 @@ GLint locationPosition = program_->GetAttributeLocation("a_position"); GLint locationMiterDirection = program_->GetAttributeLocation("a_miter_direction"); + GLint locationColor = program_->GetAttributeLocation("a_color"); float m[16]; transform.ConvertToOpenGLMatrix(m, context_.GetCanvasWidth(), context_.GetCanvasHeight()); glUniformMatrix4fv(program_->GetUniformLocation("u_matrix"), 1, GL_FALSE, m); - glUniform3f(program_->GetUniformLocation("u_color"), - data.GetRed(), data.GetGreen(), data.GetBlue()); glBindBuffer(GL_ARRAY_BUFFER, data.GetVerticesBuffer()); glEnableVertexAttribArray(locationPosition); @@ -404,6 +432,10 @@ glEnableVertexAttribArray(locationMiterDirection); glVertexAttribPointer(locationMiterDirection, COMPONENTS_MITER, GL_FLOAT, GL_FALSE, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, data.GetColorsBuffer()); + glEnableVertexAttribArray(locationColor); + glVertexAttribPointer(locationColor, COMPONENTS_COLOR, GL_FLOAT, GL_FALSE, 0, 0); + glUniform1i(program_->GetUniformLocation("u_antialiasing"), (antialiasing ? 1 : 0)); const double zoom = transform.ComputeZoom(); @@ -464,6 +496,7 @@ glDisableVertexAttribArray(locationPosition); glDisableVertexAttribArray(locationMiterDirection); + glDisableVertexAttribArray(locationColor); } } }
--- a/Framework/Scene2D/Internals/OpenGLLinesProgram.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/OpenGLLinesProgram.h Mon Jun 24 14:35:00 2019 +0200 @@ -39,12 +39,9 @@ class Segment; OpenGL::IOpenGLContext& context_; - GLuint buffers_[2]; + GLuint buffers_[3]; size_t verticesCount_; float thickness_; - float red_; - float green_; - float blue_; public: Data(OpenGL::IOpenGLContext& context, @@ -66,25 +63,12 @@ GLuint GetMiterDirectionsBuffer() const; + GLuint GetColorsBuffer() const; + float GetThickness() const { return thickness_; } - - float GetRed() const - { - return red_; - } - - float GetGreen() const - { - return green_; - } - - float GetBlue() const - { - return blue_; - } }; private:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp Mon Jun 24 14:35: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 "OpenGLLookupTableTextureRenderer.h" + +namespace OrthancStone +{ + namespace Internals + { + void OpenGLLookupTableTextureRenderer::LoadTexture( + const LookupTableTextureSceneLayer& layer) + { + const Orthanc::ImageAccessor& source = layer.GetTexture(); + const unsigned int width = source.GetWidth(); + const unsigned int height = source.GetHeight(); + + if ((texture_.get() == NULL) || + (texture_->GetWidth() != width) || + (texture_->GetHeight() != height)) + { + + texture_.reset(new Orthanc::Image( + Orthanc::PixelFormat_RGBA32, + width, + height, + false)); + } + + { + + const float a = layer.GetMinValue(); + float slope = 0; + + if (layer.GetMinValue() >= layer.GetMaxValue()) + { + slope = 0; + } + else + { + slope = 256.0f / (layer.GetMaxValue() - layer.GetMinValue()); + } + + Orthanc::ImageAccessor target; + texture_->GetWriteableAccessor(target); + + const std::vector<uint8_t>& lut = layer.GetLookupTable(); + if (lut.size() != 4 * 256) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + assert(source.GetFormat() == Orthanc::PixelFormat_Float32 && + target.GetFormat() == Orthanc::PixelFormat_RGBA32 && + sizeof(float) == 4); + + for (unsigned int y = 0; y < height; y++) + { + const float* p = reinterpret_cast<const float*>(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + float v = (*p - a) * slope; + if (v <= 0) + { + v = 0; + } + else if (v >= 255) + { + v = 255; + } + + uint8_t vv = static_cast<uint8_t>(v); + + q[0] = lut[4 * vv + 0]; // R + q[1] = lut[4 * vv + 1]; // G + q[2] = lut[4 * vv + 2]; // B + q[3] = lut[4 * vv + 3]; // A + + p++; + q += 4; + } + } + } + + context_.MakeCurrent(); + glTexture_.reset(new OpenGL::OpenGLTexture); + glTexture_->Load(*texture_, layer.IsLinearInterpolation()); + layerTransform_ = layer.GetTransform(); + } + + + OpenGLLookupTableTextureRenderer::OpenGLLookupTableTextureRenderer( + OpenGL::IOpenGLContext& context, + OpenGLColorTextureProgram& program, + const LookupTableTextureSceneLayer& layer) + : context_(context) + , program_(program) + { + LoadTexture(layer); + } + + + void OpenGLLookupTableTextureRenderer::Render(const AffineTransform2D& transform) + { + if (glTexture_.get() != NULL) + { + program_.Apply( + *glTexture_, + AffineTransform2D::Combine(transform, layerTransform_), + true); + } + } + + + void OpenGLLookupTableTextureRenderer::Update(const ISceneLayer& layer) + { + // Should never happen (no revisions in color textures) + LoadTexture(dynamic_cast<const LookupTableTextureSceneLayer&>(layer)); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,56 @@ +/** + * 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 "OpenGLColorTextureProgram.h" +#include "CompositorHelper.h" +#include "../LookupTableTextureSceneLayer.h" + +#include <Core/Images/Image.h> + +namespace OrthancStone +{ + namespace Internals + { + class OpenGLLookupTableTextureRenderer : public CompositorHelper::ILayerRenderer + { + private: + OpenGL::IOpenGLContext& context_; + OpenGLColorTextureProgram& program_; + std::auto_ptr<OpenGL::OpenGLTexture> glTexture_; + std::auto_ptr<Orthanc::Image> texture_; + AffineTransform2D layerTransform_; + + void LoadTexture(const LookupTableTextureSceneLayer& layer); + + public: + OpenGLLookupTableTextureRenderer( + OpenGL::IOpenGLContext& context, + OpenGLColorTextureProgram& program, + const LookupTableTextureSceneLayer& layer); + + virtual void Render(const AffineTransform2D& transform); + + virtual void Update(const ISceneLayer& layer); + }; + } +}
--- a/Framework/Scene2D/Internals/OpenGLTextProgram.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Internals/OpenGLTextProgram.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -75,9 +75,9 @@ const GlyphTextureAlphabet& alphabet, const TextSceneLayer& layer) : context_(context), - red_(layer.GetRedAsFloat()), - green_(layer.GetGreenAsFloat()), - blue_(layer.GetBlueAsFloat()), + red_(layer.GetColor().GetRedAsFloat()), + green_(layer.GetColor().GetGreenAsFloat()), + blue_(layer.GetColor().GetBlueAsFloat()), x_(layer.GetX()), y_(layer.GetY()), border_(layer.GetBorder()),
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/LookupTableStyleConfigurator.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,90 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 "LookupTableStyleConfigurator.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + LookupTableStyleConfigurator::LookupTableStyleConfigurator() : + revision_(0), + hasLut_(false), + hasRange_(false) + { + } + + + void LookupTableStyleConfigurator::SetLookupTable(Orthanc::EmbeddedResources::FileResourceId resource) + { + hasLut_ = true; + Orthanc::EmbeddedResources::GetFileResource(lut_, resource); + } + + + void LookupTableStyleConfigurator::SetLookupTable(const std::string& lut) + { + hasLut_ = true; + lut_ = lut; + } + + + void LookupTableStyleConfigurator::SetRange(float minValue, + float maxValue) + { + if (minValue > maxValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + hasRange_ = true; + minValue_ = minValue; + maxValue_ = maxValue; + } + } + + + TextureBaseSceneLayer* LookupTableStyleConfigurator::CreateTextureFromImage(const Orthanc::ImageAccessor& image) const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + void LookupTableStyleConfigurator::ApplyStyle(ISceneLayer& layer) const + { + LookupTableTextureSceneLayer& l = dynamic_cast<LookupTableTextureSceneLayer&>(layer); + + if (hasLut_) + { + l.SetLookupTable(lut_); + } + + if (hasRange_) + { + l.SetRange(minValue_, maxValue_); + } + else + { + l.FitRange(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/LookupTableStyleConfigurator.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,68 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 "ILayerStyleConfigurator.h" + +#include <EmbeddedResources.h> + +namespace OrthancStone +{ + /** + This configurator supplies an API to set a display range and a LUT. + */ + class LookupTableStyleConfigurator : public ILayerStyleConfigurator + { + private: + uint64_t revision_; + bool hasLut_; + std::string lut_; + bool hasRange_; + float minValue_; + float maxValue_; + + public: + LookupTableStyleConfigurator(); + + void SetLookupTable(Orthanc::EmbeddedResources::FileResourceId resource); + + void SetLookupTable(const std::string& lut); + + void SetRange(float minValue, + float maxValue); + + virtual uint64_t GetRevision() const + { + return revision_; + } + + virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const; + + virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame, + const DicomInstanceParameters& parameters) const + { + return parameters.CreateLookupTableTexture(frame); + } + + virtual void ApplyStyle(ISceneLayer& layer) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/LookupTableTextureSceneLayer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,178 @@ +/** + * 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 "LookupTableTextureSceneLayer.h" + +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + static void StringToVector(std::vector<uint8_t>& target, + const std::string& source) + { + target.resize(source.size()); + + for (size_t i = 0; i < source.size(); i++) + { + target[i] = source[i]; + } + } + + + LookupTableTextureSceneLayer::LookupTableTextureSceneLayer(const Orthanc::ImageAccessor& texture) + { + { + std::auto_ptr<Orthanc::ImageAccessor> t( + new Orthanc::Image(Orthanc::PixelFormat_Float32, + texture.GetWidth(), + texture.GetHeight(), + false)); + + Orthanc::ImageProcessing::Convert(*t, texture); + SetTexture(t.release()); + } + + SetLookupTableGrayscale(); + SetRange(0, 1); + } + + + void LookupTableTextureSceneLayer::SetLookupTableGrayscale() + { + std::vector<uint8_t> rgb(3 * 256); + + for (size_t i = 0; i < 256; i++) + { + rgb[3 * i] = static_cast<uint8_t>(i); + rgb[3 * i + 1] = static_cast<uint8_t>(i); + rgb[3 * i + 2] = static_cast<uint8_t>(i); + } + + SetLookupTableRgb(rgb); + } + + + void LookupTableTextureSceneLayer::SetLookupTableRgb(const std::vector<uint8_t>& lut) + { + if (lut.size() != 3 * 256) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + lut_.resize(4 * 256); + + for (size_t i = 0; i < 256; i++) + { + // Premultiplied alpha + + if (i == 0) + { + // Make zero transparent + lut_[4 * i] = 0; // R + lut_[4 * i + 1] = 0; // G + lut_[4 * i + 2] = 0; // B + lut_[4 * i + 3] = 0; // A + } + else + { + float a = static_cast<float>(i) / 255.0f; + + float r = static_cast<float>(lut[3 * i]) * a; + float g = static_cast<float>(lut[3 * i + 1]) * a; + float b = static_cast<float>(lut[3 * i + 2]) * a; + + lut_[4 * i] = static_cast<uint8_t>(std::floor(r)); + lut_[4 * i + 1] = static_cast<uint8_t>(std::floor(g)); + lut_[4 * i + 2] = static_cast<uint8_t>(std::floor(b)); + lut_[4 * i + 3] = static_cast<uint8_t>(std::floor(a * 255.0f)); + } + } + + IncrementRevision(); + } + + + void LookupTableTextureSceneLayer::SetLookupTable(const std::vector<uint8_t>& lut) + { + if (lut.size() == 4 * 256) + { + lut_ = lut; + IncrementRevision(); + } + else if (lut.size() == 3 * 256) + { + SetLookupTableRgb(lut); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + void LookupTableTextureSceneLayer::SetLookupTable(const std::string& lut) + { + std::vector<uint8_t> tmp; + StringToVector(tmp, lut); + SetLookupTable(tmp); + } + + + void LookupTableTextureSceneLayer::SetRange(float minValue, + float maxValue) + { + if (minValue > maxValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + minValue_ = minValue; + maxValue_ = maxValue; + IncrementRevision(); + } + } + + + void LookupTableTextureSceneLayer::FitRange() + { + Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue_, maxValue_, GetTexture()); + assert(minValue_ <= maxValue_); + + IncrementRevision(); + } + + + ISceneLayer* LookupTableTextureSceneLayer::Clone() const + { + std::auto_ptr<LookupTableTextureSceneLayer> cloned + (new LookupTableTextureSceneLayer(GetTexture())); + + cloned->CopyParameters(*this); + cloned->minValue_ = minValue_; + cloned->maxValue_ = maxValue_; + cloned->lut_ = lut_; + + return cloned.release(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/LookupTableTextureSceneLayer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,78 @@ +/** + * 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 "TextureBaseSceneLayer.h" + +namespace OrthancStone +{ + class LookupTableTextureSceneLayer : public TextureBaseSceneLayer + { + private: + ImageWindowing windowing_; + float minValue_; + float maxValue_; + std::vector<uint8_t> lut_; + + void SetLookupTableRgb(const std::vector<uint8_t>& lut); + + public: + // The pixel format must be convertible to Float32 + LookupTableTextureSceneLayer(const Orthanc::ImageAccessor& texture); + + void SetLookupTableGrayscale(); + + // The vector must contain either 3 * 256 values (RGB), or 4 * 256 + // (RGBA). In the RGB case, an alpha channel will be automatically added. + void SetLookupTable(const std::vector<uint8_t>& lut); + + void SetLookupTable(const std::string& lut); + + void SetRange(float minValue, + float maxValue); + + void FitRange(); + + float GetMinValue() const + { + return minValue_; + } + + float GetMaxValue() const + { + return maxValue_; + } + + // This returns a vector of 4 * 256 values between 0 and 255, in RGBA. + const std::vector<uint8_t>& GetLookupTable() const + { + return lut_; + } + + virtual ISceneLayer* Clone() const; + + virtual Type GetType() const + { + return Type_LookupTableTexture; + } + }; +}
--- a/Framework/Scene2D/OpenGLCompositor.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/OpenGLCompositor.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -26,6 +26,7 @@ #include "Internals/OpenGLColorTextureRenderer.h" #include "Internals/OpenGLFloatTextureRenderer.h" #include "Internals/OpenGLInfoPanelRenderer.h" +#include "Internals/OpenGLLookupTableTextureRenderer.h" #include "Internals/OpenGLTextRenderer.h" namespace OrthancStone @@ -92,6 +93,10 @@ return new Internals::OpenGLFloatTextureRenderer (context_, floatTextureProgram_, dynamic_cast<const FloatTextureSceneLayer&>(layer)); + case ISceneLayer::Type_LookupTableTexture: + return new Internals::OpenGLLookupTableTextureRenderer + (context_, colorTextureProgram_, dynamic_cast<const LookupTableTextureSceneLayer&>(layer)); + case ISceneLayer::Type_Polyline: return new Internals::OpenGLAdvancedPolylineRenderer (context_, linesProgram_, dynamic_cast<const PolylineSceneLayer&>(layer));
--- a/Framework/Scene2D/PanSceneTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/PanSceneTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -20,11 +20,11 @@ #include "PanSceneTracker.h" -#include <Framework/Scene2DViewport/ViewportController.h> +#include "../Scene2DViewport/ViewportController.h" namespace OrthancStone { - PanSceneTracker::PanSceneTracker(ViewportControllerWPtr controllerW, + PanSceneTracker::PanSceneTracker(boost::weak_ptr<ViewportController> controllerW, const PointerEvent& event) : OneGesturePointerTracker(controllerW) , originalSceneToCanvas_(GetController()->GetSceneToCanvasTransform())
--- a/Framework/Scene2D/PanSceneTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/PanSceneTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -25,19 +25,17 @@ namespace OrthancStone { - class ViewportController; - class PanSceneTracker : public OneGesturePointerTracker { public: - PanSceneTracker(ViewportControllerWPtr controllerW, + PanSceneTracker(boost::weak_ptr<ViewportController> controllerW, const PointerEvent& event); virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE; virtual void Cancel() ORTHANC_OVERRIDE; private: - ViewportControllerWPtr controllerW_; + boost::weak_ptr<ViewportController> controllerW_; ScenePoint2D pivot_; AffineTransform2D originalSceneToCanvas_; AffineTransform2D originalCanvasToScene_;
--- a/Framework/Scene2D/PolylineSceneLayer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/PolylineSceneLayer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -25,6 +25,14 @@ namespace OrthancStone { + void PolylineSceneLayer::Copy(const PolylineSceneLayer& other) + { + items_ = other.items_; + thickness_ = other.thickness_; + revision_ ++; + } + + ISceneLayer* PolylineSceneLayer::Clone() const { std::auto_ptr<PolylineSceneLayer> cloned(new PolylineSceneLayer); @@ -42,52 +50,40 @@ else { thickness_ = thickness; - BumpRevision(); + revision_++; } } - void PolylineSceneLayer::Copy(const PolylineSceneLayer& from) - { - SetColor(from.GetRed(), from.GetGreen(), from.GetBlue()); - chains_ = from.chains_; - closed_ = from.closed_; - thickness_ = from.thickness_; - BumpRevision(); - } - - - void PolylineSceneLayer::Reserve(size_t countChains) - { - chains_.reserve(countChains); - closed_.reserve(countChains); - } - - void PolylineSceneLayer::AddChain(const Chain& chain, - bool isClosed) + bool isClosed, + uint8_t red, + uint8_t green, + uint8_t blue) { if (!chain.empty()) { - chains_.push_back(chain); - closed_.push_back(isClosed); - BumpRevision(); + items_.push_back(Item()); + items_.back().chain_ = chain; + items_.back().closed_ = isClosed; + items_.back().color_ = Color(red, green, blue); + + revision_++; } } void PolylineSceneLayer::ClearAllChains() { - chains_.clear(); - closed_.clear(); - BumpRevision(); + items_.clear(); + revision_++; } - const PolylineSceneLayer::Chain& PolylineSceneLayer::GetChain(size_t i) const + const PolylineSceneLayer::Item& PolylineSceneLayer::GetItem(size_t i) const { - if (i < chains_.size()) + if (i < items_.size()) { - return chains_[i]; + return items_[i]; } else { @@ -96,28 +92,15 @@ } - bool PolylineSceneLayer::IsClosedChain(size_t i) const - { - if (i < closed_.size()) - { - return closed_[i]; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - bool PolylineSceneLayer::GetBoundingBox(Extent2D& target) const { target.Reset(); - for (size_t i = 0; i < chains_.size(); i++) + for (size_t i = 0; i < items_.size(); i++) { - for (size_t j = 0; j < chains_[i].size(); j++) + for (size_t j = 0; j < items_[i].chain_.size(); j++) { - const ScenePoint2D& p = chains_[i][j]; + const ScenePoint2D& p = items_[i].chain_[j]; target.AddPoint(p.GetX(), p.GetY()); } }
--- a/Framework/Scene2D/PolylineSceneLayer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/PolylineSceneLayer.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,29 +21,47 @@ #pragma once -#include "ColorSceneLayer.h" +#include "Color.h" #include "ScenePoint2D.h" +#include "ISceneLayer.h" #include <vector> namespace OrthancStone { - class PolylineSceneLayer : public ColorSceneLayer + class PolylineSceneLayer : public ISceneLayer { public: typedef std::vector<ScenePoint2D> Chain; private: - std::vector<Chain> chains_; - std::vector<bool> closed_; - double thickness_; + struct Item + { + Chain chain_; + bool closed_; + Color color_; + }; + + std::vector<Item> items_; + double thickness_; + uint64_t revision_; + + const Item& GetItem(size_t i) const; public: PolylineSceneLayer() : - thickness_(1.0) + thickness_(1.0), + revision_(0) { } + void Copy(const PolylineSceneLayer& other); + + virtual uint64_t GetRevision() const + { + return revision_; + } + virtual ISceneLayer* Clone() const; void SetThickness(double thickness); @@ -53,23 +71,45 @@ return thickness_; } - void Copy(const PolylineSceneLayer& from); - - void Reserve(size_t countChains); + void Reserve(size_t countChains) + { + items_.reserve(countChains); + } void AddChain(const Chain& chain, - bool isClosed); + bool isClosed, + uint8_t red, + uint8_t green, + uint8_t blue); + + void AddChain(const Chain& chain, + bool isClosed, + const Color& color) + { + AddChain(chain, isClosed, color.GetRed(), color.GetGreen(), color.GetBlue()); + } void ClearAllChains(); size_t GetChainsCount() const { - return chains_.size(); + return items_.size(); + } + + const Chain& GetChain(size_t i) const + { + return GetItem(i).chain_; } - const Chain& GetChain(size_t i) const; + bool IsClosedChain(size_t i) const + { + return GetItem(i).closed_; + } - bool IsClosedChain(size_t i) const; + const Color& GetColor(size_t i) const + { + return GetItem(i).color_; + } virtual Type GetType() const {
--- a/Framework/Scene2D/RotateSceneTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/RotateSceneTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -19,11 +19,11 @@ **/ #include "RotateSceneTracker.h" -#include <Framework/Scene2DViewport/ViewportController.h> +#include "../Scene2DViewport/ViewportController.h" namespace OrthancStone { - RotateSceneTracker::RotateSceneTracker(ViewportControllerWPtr controllerW, + RotateSceneTracker::RotateSceneTracker(boost::weak_ptr<ViewportController> controllerW, const PointerEvent& event) : OneGesturePointerTracker(controllerW) , click_(event.GetMainPosition())
--- a/Framework/Scene2D/RotateSceneTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/RotateSceneTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,15 +23,14 @@ #include "../Scene2DViewport/OneGesturePointerTracker.h" #include "Internals/FixedPointAligner.h" +#include <memory> namespace OrthancStone { - class ViewportController; - class RotateSceneTracker : public OneGesturePointerTracker { public: - RotateSceneTracker(ViewportControllerWPtr controllerW, + RotateSceneTracker(boost::weak_ptr<ViewportController> controllerW, const PointerEvent& event); virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE;
--- a/Framework/Scene2D/Scene2D.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Scene2D.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -102,8 +102,7 @@ void Scene2D::SetLayer(int depth, ISceneLayer* layer) // Takes ownership { - LOG(INFO) << "SetLayer(" << depth << ", " << - reinterpret_cast<intptr_t>(layer) << ")"; + LOG(TRACE) << "SetLayer(" << depth << ", " << reinterpret_cast<intptr_t>(layer) << ")"; std::auto_ptr<Item> item(new Item(layer, layerCounter_++)); if (layer == NULL)
--- a/Framework/Scene2D/Scene2D.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/Scene2D.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,8 +23,8 @@ #include "ISceneLayer.h" #include "../Toolbox/AffineTransform2D.h" -#include <Framework/Messages/IObservable.h> -#include <Framework/Messages/IMessage.h> +#include "../Messages/IObservable.h" +#include "../Messages/IMessage.h" #include <map>
--- a/Framework/Scene2D/TextSceneLayer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/TextSceneLayer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -37,7 +37,7 @@ ISceneLayer* TextSceneLayer::Clone() const { std::auto_ptr<TextSceneLayer> cloned(new TextSceneLayer); - cloned->SetColor(GetRed(), GetGreen(), GetBlue()); + cloned->SetColor(GetColor()); cloned->x_ = x_; cloned->y_ = y_; cloned->utf8_ = utf8_;
--- a/Framework/Scene2D/ZoomSceneTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/ZoomSceneTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -20,11 +20,14 @@ #include "ZoomSceneTracker.h" -#include <Framework/Scene2DViewport/ViewportController.h> +#include "../Scene2DViewport/ViewportController.h" + +using boost::weak_ptr; +using boost::shared_ptr; namespace OrthancStone { - ZoomSceneTracker::ZoomSceneTracker(ViewportControllerWPtr controllerW, + ZoomSceneTracker::ZoomSceneTracker(weak_ptr<ViewportController> controllerW, const PointerEvent& event, unsigned int canvasHeight) : OneGesturePointerTracker(controllerW)
--- a/Framework/Scene2D/ZoomSceneTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2D/ZoomSceneTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -27,12 +27,10 @@ namespace OrthancStone { - class Scene2D; - class ZoomSceneTracker : public OneGesturePointerTracker { public: - ZoomSceneTracker(ViewportControllerWPtr controllerW, + ZoomSceneTracker(boost::weak_ptr<ViewportController> controllerW, const PointerEvent& event, unsigned int canvasHeight);
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -20,15 +20,33 @@ #include "AngleMeasureTool.h" #include "MeasureToolsToolbox.h" +#include "LayerHolder.h" #include <Core/Logging.h> #include <boost/math/constants/constants.hpp> +#include <boost/make_shared.hpp> -extern void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value); +//// <HACK> +//// REMOVE THIS +//#ifndef NDEBUG +//extern void +//TrackerSample_SetInfoDisplayMessage(std::string key, std::string value); +//#endif +//// </HACK> namespace OrthancStone { + // the params in the LayerHolder ctor specify the number of polyline and text + // layers + AngleMeasureTool::AngleMeasureTool( + MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW) + : MeasureTool(broker, controllerW) + , layerHolder_(boost::make_shared<LayerHolder>(controllerW,1,5)) + { + + } + AngleMeasureTool::~AngleMeasureTool() { // this measuring tool is a RABI for the corresponding visual layers @@ -39,12 +57,9 @@ void AngleMeasureTool::RemoveFromScene() { - if (layersCreated) + if (layerHolder_->AreLayersCreated() && IsSceneAlive()) { - assert(GetScene()->HasLayer(polylineZIndex_)); - assert(GetScene()->HasLayer(textBaseZIndex_)); - GetScene()->DeleteLayer(polylineZIndex_); - GetScene()->DeleteLayer(textBaseZIndex_); + layerHolder_->DeleteLayers(); } } @@ -60,84 +75,33 @@ RefreshScene(); } + + bool AngleMeasureTool::HitTest(ScenePoint2D p) const + { + throw std::logic_error("The method or operation is not implemented."); + } + void AngleMeasureTool::SetCenter(ScenePoint2D pt) { center_ = pt; RefreshScene(); } - PolylineSceneLayer* AngleMeasureTool::GetPolylineLayer() - { - assert(GetScene()->HasLayer(polylineZIndex_)); - ISceneLayer* layer = &(GetScene()->GetLayer(polylineZIndex_)); - PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer); - assert(concreteLayer != NULL); - return concreteLayer; - } - void AngleMeasureTool::RefreshScene() { if (IsSceneAlive()) { - + boost::shared_ptr<ViewportController> controller = GetController(); if (IsEnabled()) { - // get the scaling factor - const double pixelToScene = - GetScene()->GetCanvasToSceneTransform().ComputeZoom(); - - if (!layersCreated) - { - // Create the layers if need be - - assert(textBaseZIndex_ == -1); - { - polylineZIndex_ = GetScene()->GetMaxDepth() + 100; - //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_; - std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer()); - GetScene()->SetLayer(polylineZIndex_, layer.release()); + layerHolder_->CreateLayersIfNeeded(); - } - { - textBaseZIndex_ = GetScene()->GetMaxDepth() + 100; - // create the four text background layers - { - std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer()); - GetScene()->SetLayer(textBaseZIndex_, layer.release()); - } - { - std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer()); - GetScene()->SetLayer(textBaseZIndex_ + 1, layer.release()); - } - { - std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer()); - GetScene()->SetLayer(textBaseZIndex_ + 2, layer.release()); - } - { - std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer()); - GetScene()->SetLayer(textBaseZIndex_ + 3, layer.release()); - } + { + // Fill the polyline layer with the measurement lines + PolylineSceneLayer* polylineLayer = layerHolder_->GetPolylineLayer(0); + polylineLayer->ClearAllChains(); - // and the text layer itself - { - std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer()); - GetScene()->SetLayer(textBaseZIndex_ + 4, layer.release()); - } - - } - layersCreated = true; - } - else - { - assert(GetScene()->HasLayer(polylineZIndex_)); - assert(GetScene()->HasLayer(textBaseZIndex_)); - } - { - // Fill the polyline layer with the measurement line - - PolylineSceneLayer* polylineLayer = GetPolylineLayer(); - polylineLayer->ClearAllChains(); - polylineLayer->SetColor(0, 183, 17); + const Color color(0, 183, 17); // sides { @@ -145,41 +109,41 @@ PolylineSceneLayer::Chain chain; chain.push_back(side1End_); chain.push_back(center_); - polylineLayer->AddChain(chain, false); + polylineLayer->AddChain(chain, false, color); } { PolylineSceneLayer::Chain chain; chain.push_back(side2End_); chain.push_back(center_); - polylineLayer->AddChain(chain, false); + polylineLayer->AddChain(chain, false, color); } } - // handles + // Create the handles { - //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength) - { PolylineSceneLayer::Chain chain; - AddSquare(chain, *GetScene(), side1End_, 10.0 * pixelToScene); //TODO: take DPI into account - polylineLayer->AddChain(chain, true); + //TODO: take DPI into account + AddSquare(chain, GetScene(), side1End_, + GetController()->GetHandleSideLengthS()); + polylineLayer->AddChain(chain, true, color); } - { PolylineSceneLayer::Chain chain; - AddSquare(chain, *GetScene(), side2End_, 10.0 * pixelToScene); //TODO: take DPI into account - polylineLayer->AddChain(chain, true); + //TODO: take DPI into account + AddSquare(chain, GetScene(), side2End_, + GetController()->GetHandleSideLengthS()); + polylineLayer->AddChain(chain, true, color); } } - // arc + // Create the arc { PolylineSceneLayer::Chain chain; - const double ARC_RADIUS_CANVAS_COORD = 30.0; - AddShortestArc(chain, *GetScene(), side1End_, center_, side2End_, - ARC_RADIUS_CANVAS_COORD * pixelToScene); - polylineLayer->AddChain(chain, false); + AddShortestArc(chain, side1End_, center_, side2End_, + controller->GetAngleToolArcRadiusS()); + polylineLayer->AddChain(chain, false, color); } } { @@ -189,25 +153,18 @@ side1End_.GetY() - center_.GetY(), side1End_.GetX() - center_.GetX()); - double p2cAngle = atan2( side2End_.GetY() - center_.GetY(), side2End_.GetX() - center_.GetX()); double delta = NormalizeAngle(p2cAngle - p1cAngle); - - double theta = p1cAngle + delta / 2; - - const double TEXT_CENTER_DISTANCE_CANVAS_COORD = 90; - - double offsetX = TEXT_CENTER_DISTANCE_CANVAS_COORD * cos(theta); + double ox = controller->GetAngleTopTextLabelDistanceS() * cos(theta); + double oy = controller->GetAngleTopTextLabelDistanceS() * sin(theta); - double offsetY = TEXT_CENTER_DISTANCE_CANVAS_COORD * sin(theta); - - double pointX = center_.GetX() + offsetX * pixelToScene; - double pointY = center_.GetY() + offsetY * pixelToScene; + double pointX = center_.GetX() + ox; + double pointY = center_.GetY() + oy; char buf[64]; double angleDeg = RadiansToDegrees(delta); @@ -216,8 +173,9 @@ sprintf(buf, "%0.02f\xc2\xb0", angleDeg); SetTextLayerOutlineProperties( - *GetScene(), textBaseZIndex_, buf, ScenePoint2D(pointX, pointY)); + GetScene(), layerHolder_, buf, ScenePoint2D(pointX, pointY)); +#if 0 // TODO:make it togglable bool enableInfoDisplay = false; if (enableInfoDisplay) @@ -252,11 +210,11 @@ TrackerSample_SetInfoDisplayMessage("p2cAngle (deg)", boost::lexical_cast<std::string>(RadiansToDegrees(p2cAngle))); - TrackerSample_SetInfoDisplayMessage("offsetX (pix)", - boost::lexical_cast<std::string>(offsetX)); + TrackerSample_SetInfoDisplayMessage("ox (scene)", + boost::lexical_cast<std::string>(ox)); - TrackerSample_SetInfoDisplayMessage("offsetY (pix)", - boost::lexical_cast<std::string>(offsetY)); + TrackerSample_SetInfoDisplayMessage("offsetY (scene)", + boost::lexical_cast<std::string>(oy)); TrackerSample_SetInfoDisplayMessage("pointX", boost::lexical_cast<std::string>(pointX)); @@ -267,18 +225,12 @@ TrackerSample_SetInfoDisplayMessage("angleDeg", boost::lexical_cast<std::string>(angleDeg)); } - - - +#endif } } else { - if (layersCreated) - { - RemoveFromScene(); - layersCreated = false; - } + RemoveFromScene(); } } }
--- a/Framework/Scene2DViewport/AngleMeasureTool.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/AngleMeasureTool.h Mon Jun 24 14:35:00 2019 +0200 @@ -20,12 +20,13 @@ #pragma once -#include "MeasureTools.h" +#include "MeasureTool.h" -#include <Framework/Scene2D/Scene2D.h> -#include <Framework/Scene2D/ScenePoint2D.h> -#include <Framework/Scene2D/PolylineSceneLayer.h> -#include <Framework/Scene2D/TextSceneLayer.h> +#include "../Scene2DViewport/LayerHolder.h" +#include "../Scene2D/Scene2D.h" +#include "../Scene2D/ScenePoint2D.h" +#include "../Scene2D/PolylineSceneLayer.h" +#include "../Scene2D/TextSceneLayer.h" #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> @@ -38,14 +39,7 @@ class AngleMeasureTool : public MeasureTool { public: - AngleMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW) - : MeasureTool(broker, controllerW) - , layersCreated(false) - , polylineZIndex_(-1) - , textBaseZIndex_(-1) - { - - } + AngleMeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW); ~AngleMeasureTool(); @@ -53,24 +47,19 @@ void SetCenter(ScenePoint2D start); void SetSide2End(ScenePoint2D start); + + virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE; + private: - PolylineSceneLayer* GetPolylineLayer(); - - // 0 --> 3 are for the text background (outline) - // 4 is for the actual text - TextSceneLayer* GetTextLayer(int index); virtual void RefreshScene() ORTHANC_OVERRIDE; void RemoveFromScene(); private: - ScenePoint2D side1End_; - ScenePoint2D side2End_; - ScenePoint2D center_; - bool layersCreated; - int polylineZIndex_; - int textBaseZIndex_; + ScenePoint2D side1End_; + ScenePoint2D side2End_; + ScenePoint2D center_; + boost::shared_ptr<LayerHolder> layerHolder_; }; - }
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -21,13 +21,11 @@ #include "CreateAngleMeasureTracker.h" #include <Core/OrthancException.h> -using namespace Orthanc; - namespace OrthancStone { CreateAngleMeasureTracker::CreateAngleMeasureTracker( MessageBroker& broker, - ViewportControllerWPtr controllerW, + boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e) : CreateMeasureTracker(controllerW) , state_(CreatingSide1) @@ -49,7 +47,7 @@ if (!alive_) { - throw OrthancException(ErrorCode_InternalError, + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Internal error: wrong state in CreateAngleMeasureTracker::" "PointerMove: active_ == false"); } @@ -66,7 +64,7 @@ GetCommand()->SetSide2End(scenePos); break; default: - throw OrthancException(ErrorCode_InternalError, + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong state in CreateAngleMeasureTracker::" "PointerMove: state_ invalid"); } @@ -88,12 +86,12 @@ state_ = CreatingSide2; break; case CreatingSide2: - throw OrthancException(ErrorCode_InternalError, + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong state in CreateAngleMeasureTracker::" "PointerUp: state_ == CreatingSide2 ; this should not happen"); break; default: - throw OrthancException(ErrorCode_InternalError, + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong state in CreateAngleMeasureTracker::" "PointerMove: state_ invalid"); } @@ -104,7 +102,7 @@ switch (state_) { case CreatingSide1: - throw OrthancException(ErrorCode_InternalError, + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong state in CreateAngleMeasureTracker::" "PointerDown: state_ == CreatingSide1 ; this should not happen"); break; @@ -113,13 +111,13 @@ alive_ = false; break; default: - throw OrthancException(ErrorCode_InternalError, + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong state in CreateAngleMeasureTracker::" "PointerMove: state_ invalid"); } } - CreateAngleMeasureCommandPtr CreateAngleMeasureTracker::GetCommand() + boost::shared_ptr<CreateAngleMeasureCommand> CreateAngleMeasureTracker::GetCommand() { return boost::dynamic_pointer_cast<CreateAngleMeasureCommand>(command_); }
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -39,7 +39,7 @@ */ CreateAngleMeasureTracker( MessageBroker& broker, - ViewportControllerWPtr controllerW, + boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e); ~CreateAngleMeasureTracker(); @@ -49,7 +49,7 @@ virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE; private: - CreateAngleMeasureCommandPtr GetCommand(); + boost::shared_ptr<CreateAngleMeasureCommand> GetCommand(); enum State {
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -21,13 +21,11 @@ #include "CreateLineMeasureTracker.h" #include <Core/OrthancException.h> -using namespace Orthanc; - namespace OrthancStone { CreateLineMeasureTracker::CreateLineMeasureTracker( MessageBroker& broker, - ViewportControllerWPtr controllerW, + boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e) : CreateMeasureTracker(controllerW) { @@ -49,7 +47,7 @@ if (!alive_) { - throw OrthancException(ErrorCode_InternalError, + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Internal error: wrong state in CreateLineMeasureTracker::" "PointerMove: active_ == false"); } @@ -82,7 +80,7 @@ "are ignored when the line measure creation tracker is active"; } - CreateLineMeasureCommandPtr CreateLineMeasureTracker::GetCommand() + boost::shared_ptr<CreateLineMeasureCommand> CreateLineMeasureTracker::GetCommand() { return boost::dynamic_pointer_cast<CreateLineMeasureCommand>(command_); }
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -36,7 +36,7 @@ */ CreateLineMeasureTracker( MessageBroker& broker, - ViewportControllerWPtr controllerW, + boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e); ~CreateLineMeasureTracker(); @@ -46,6 +46,6 @@ virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE; private: - CreateLineMeasureCommandPtr GetCommand(); + boost::shared_ptr<CreateLineMeasureCommand> GetCommand(); }; }
--- a/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -19,7 +19,7 @@ **/ #include "IFlexiblePointerTracker.h" -#include <Framework/Scene2D/IPointerTracker.h> +#include "../Scene2D/IPointerTracker.h" namespace OrthancStone @@ -30,7 +30,7 @@ class SimpleTrackerAdapter : public IFlexiblePointerTracker { public: - SimpleTrackerAdapter(PointerTrackerPtr wrappedTracker) + SimpleTrackerAdapter(boost::shared_ptr<IPointerTracker> wrappedTracker) : wrappedTracker_(wrappedTracker) , active_(true) { @@ -66,14 +66,14 @@ } private: - PointerTrackerPtr wrappedTracker_; + boost::shared_ptr<IPointerTracker> wrappedTracker_; bool active_; }; } - FlexiblePointerTrackerPtr CreateSimpleTrackerAdapter(PointerTrackerPtr t) + boost::shared_ptr<IFlexiblePointerTracker> CreateSimpleTrackerAdapter(boost::shared_ptr<IPointerTracker> t) { - return FlexiblePointerTrackerPtr(new SimpleTrackerAdapter(t)); + return boost::shared_ptr<IFlexiblePointerTracker>(new SimpleTrackerAdapter(t)); } #endif }
--- a/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +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/>. - **/ - -namespace OrthancStone -{ -}
--- a/Framework/Scene2DViewport/EditAngleMeasureTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +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 - -namespace OrthancStone -{ -}
--- a/Framework/Scene2DViewport/EditCircleMeasureTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +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/>. - **/ - -namespace OrthancStone -{ -}
--- a/Framework/Scene2DViewport/EditCircleMeasureTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +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 - -namespace OrthancStone -{ -}
--- a/Framework/Scene2DViewport/EditLineMeasureTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +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/>. - **/ - -namespace OrthancStone -{ -}
--- a/Framework/Scene2DViewport/EditLineMeasureTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +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 - -namespace OrthancStone -{ -}
--- a/Framework/Scene2DViewport/IFlexiblePointerTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/IFlexiblePointerTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,9 +21,10 @@ #pragma once -#include "PointerTypes.h" +#include "PredeclaredTypes.h" -#include <Framework/Scene2D/PointerEvent.h> +#include "../Scene2D/PointerEvent.h" + namespace OrthancStone { @@ -82,6 +83,6 @@ This factory adopts the supplied simple tracker and creates a flexible tracker wrapper around it. */ - FlexiblePointerTrackerPtr CreateSimpleTrackerAdapter(PointerTrackerPtr); + boost::shared_ptr<IFlexiblePointerTracker> CreateSimpleTrackerAdapter(boost::shared_ptr<IPointerTracker>); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/LayerHolder.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,139 @@ +/** + * 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 "LayerHolder.h" +#include "../Scene2D/TextSceneLayer.h" +#include "../Scene2D/PolylineSceneLayer.h" +#include "../Scene2D/Scene2D.h" +#include "../Scene2DViewport/ViewportController.h" +#include "../StoneException.h" + +namespace OrthancStone +{ + LayerHolder::LayerHolder( + boost::weak_ptr<ViewportController> controllerW, + int polylineLayerCount, + int textLayerCount) + : textLayerCount_(textLayerCount) + , polylineLayerCount_(polylineLayerCount) + , controllerW_(controllerW) + , baseLayerIndex_(-1) + { + + } + + void LayerHolder::CreateLayers() + { + assert(baseLayerIndex_ == -1); + + baseLayerIndex_ = GetScene()->GetMaxDepth() + 100; + + for (int i = 0; i < polylineLayerCount_; ++i) + { + std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer()); + GetScene()->SetLayer(baseLayerIndex_ + i, layer.release()); + } + + for (int i = 0; i < textLayerCount_; ++i) + { + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer()); + GetScene()->SetLayer( + baseLayerIndex_ + polylineLayerCount_ + i, + layer.release()); + } + + } + + void LayerHolder::CreateLayersIfNeeded() + { + if (baseLayerIndex_ == -1) + CreateLayers(); + } + + bool LayerHolder::AreLayersCreated() const + { + return (baseLayerIndex_ != -1); + } + + boost::shared_ptr<Scene2D> LayerHolder::GetScene() + { + boost::shared_ptr<ViewportController> controller = controllerW_.lock(); + ORTHANC_ASSERT(controller.get() != 0, "Zombie attack!"); + return controller->GetScene(); + } + + void LayerHolder::DeleteLayers() + { + for (int i = 0; i < textLayerCount_ + polylineLayerCount_; ++i) + { + ORTHANC_ASSERT(GetScene()->HasLayer(baseLayerIndex_ + i), "No layer"); + GetScene()->DeleteLayer(baseLayerIndex_ + i); + } + baseLayerIndex_ = -1; + } + + PolylineSceneLayer* LayerHolder::GetPolylineLayer(int index /*= 0*/) + { + using namespace Orthanc; + ORTHANC_ASSERT(baseLayerIndex_ != -1); + ORTHANC_ASSERT(GetScene()->HasLayer(GetPolylineLayerIndex(index))); + ISceneLayer* layer = + &(GetScene()->GetLayer(GetPolylineLayerIndex(index))); + + PolylineSceneLayer* concreteLayer = + dynamic_cast<PolylineSceneLayer*>(layer); + + ORTHANC_ASSERT(concreteLayer != NULL); + return concreteLayer; + } + + TextSceneLayer* LayerHolder::GetTextLayer(int index /*= 0*/) + { + using namespace Orthanc; + ORTHANC_ASSERT(baseLayerIndex_ != -1); + ORTHANC_ASSERT(GetScene()->HasLayer(GetTextLayerIndex(index))); + ISceneLayer* layer = + &(GetScene()->GetLayer(GetTextLayerIndex(index))); + + TextSceneLayer* concreteLayer = + dynamic_cast<TextSceneLayer*>(layer); + + ORTHANC_ASSERT(concreteLayer != NULL); + return concreteLayer; + } + + int LayerHolder::GetPolylineLayerIndex(int index /*= 0*/) + { + using namespace Orthanc; + ORTHANC_ASSERT(index < polylineLayerCount_); + return baseLayerIndex_ + index; + } + + + int LayerHolder::GetTextLayerIndex(int index /*= 0*/) + { + using namespace Orthanc; + ORTHANC_ASSERT(index < textLayerCount_); + + // the text layers are placed right after the polyline layers + // this means they are drawn ON TOP + return baseLayerIndex_ + polylineLayerCount_ + index; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/LayerHolder.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,100 @@ +/** + * 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 "PredeclaredTypes.h" + +#include "boost/noncopyable.hpp" +#include "boost/weak_ptr.hpp" +#include "boost/shared_ptr.hpp" + +namespace OrthancStone +{ + class PolylineSceneLayer; + class TextSceneLayer; + + /** + This class holds the indices of a set a layer and supplies + getters to the concrete layer objects. Sounds very ad hoc, and it is. + */ + class LayerHolder : public boost::noncopyable + { + public: + /** + This ctor merely stores the scene and layer counts. No layer creation + performed at this time + */ + LayerHolder( + boost::weak_ptr<ViewportController> controllerW, + int polylineLayerCount, int textLayerCount); + + /** + This actually creates the layers + */ + void CreateLayers(); + + /** + This creates the layers if they are not created yet. Can be useful in + some scenarios + */ + void CreateLayersIfNeeded(); + + /** + Whether the various text and polylines layers have all been created or + none at all + */ + bool AreLayersCreated() const; + + /** + This removes the layers from the scene + */ + void DeleteLayers(); + + /** + Please note that the returned pointer belongs to the scene.Don't you dare + storing or deleting it, you fool! + + This throws if the index is not valid or if the layers are not created or + have been deleted + */ + PolylineSceneLayer* GetPolylineLayer(int index = 0); + + /** + Please note that the returned pointer belongs to the scene. Don't you dare + storing or deleting it, you fool! + + This throws if the index is not valid or if the layers are not created or + have been deleted + */ + TextSceneLayer* GetTextLayer(int index = 0); + + private: + int GetPolylineLayerIndex(int index = 0); + int GetTextLayerIndex(int index = 0); + boost::shared_ptr<Scene2D> GetScene(); + + int textLayerCount_; + int polylineLayerCount_; + boost::weak_ptr<ViewportController> controllerW_; + int baseLayerIndex_; + }; +} +
--- a/Framework/Scene2DViewport/LineMeasureTool.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/LineMeasureTool.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -20,12 +20,23 @@ #include "LineMeasureTool.h" #include "MeasureToolsToolbox.h" +#include "LayerHolder.h" #include <Core/Logging.h> +#include <boost/make_shared.hpp> namespace OrthancStone { + + LineMeasureTool::LineMeasureTool( + MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW) + : MeasureTool(broker, controllerW) + , layerHolder_(boost::make_shared<LayerHolder>(controllerW, 1, 5)) + { + + } + LineMeasureTool::~LineMeasureTool() { // this measuring tool is a RABI for the corresponding visual layers @@ -36,16 +47,12 @@ void LineMeasureTool::RemoveFromScene() { - if (layersCreated) + if (layerHolder_->AreLayersCreated() && IsSceneAlive()) { - assert(GetScene()->HasLayer(polylineZIndex_)); - assert(GetScene()->HasLayer(textZIndex_)); - GetScene()->DeleteLayer(polylineZIndex_); - GetScene()->DeleteLayer(textZIndex_); + layerHolder_->DeleteLayers(); } } - - + void LineMeasureTool::SetStart(ScenePoint2D start) { start_ = start; @@ -65,22 +72,26 @@ RefreshScene(); } - PolylineSceneLayer* LineMeasureTool::GetPolylineLayer() + + + bool LineMeasureTool::HitTest(ScenePoint2D p) const { - assert(GetScene()->HasLayer(polylineZIndex_)); - ISceneLayer* layer = &(GetScene()->GetLayer(polylineZIndex_)); - PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer); - assert(concreteLayer != NULL); - return concreteLayer; - } + const double pixelToScene = + GetScene()->GetCanvasToSceneTransform().ComputeZoom(); + + // the hit test will return true if the supplied point (in scene coords) + // is close to the handle or to the line. - TextSceneLayer* LineMeasureTool::GetTextLayer() - { - assert(GetScene()->HasLayer(textZIndex_)); - ISceneLayer* layer = &(GetScene()->GetLayer(textZIndex_)); - TextSceneLayer* concreteLayer = dynamic_cast<TextSceneLayer*>(layer); - assert(concreteLayer != NULL); - return concreteLayer; + // since the handle is small, a nice approximation is to defined this + // as a threshold on the distance between the point and the handle center. + + // this threshold is defined as a constant value in CANVAS units. + + + // line equation from two points (non-normalized) + // (y0-y1)*x + (x1-x0)*xy + (x0*y1 - x1*y0) = 0 + // + return false; } void LineMeasureTool::RefreshScene() @@ -89,113 +100,71 @@ { if (IsEnabled()) { - if (!layersCreated) - { - // Create the layers if need be + layerHolder_->CreateLayersIfNeeded(); - assert(textZIndex_ == -1); - { - polylineZIndex_ = GetScene()->GetMaxDepth() + 100; - //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_; - std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer()); - GetScene()->SetLayer(polylineZIndex_, layer.release()); - } - { - textZIndex_ = GetScene()->GetMaxDepth() + 100; - //LOG(INFO) << "set textZIndex_ to: " << textZIndex_; - std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer()); - GetScene()->SetLayer(textZIndex_, layer.release()); - } - layersCreated = true; - } - else - { - assert(GetScene()->HasLayer(polylineZIndex_)); - assert(GetScene()->HasLayer(textZIndex_)); - } { // Fill the polyline layer with the measurement line - PolylineSceneLayer* polylineLayer = GetPolylineLayer(); + PolylineSceneLayer* polylineLayer = layerHolder_->GetPolylineLayer(0); polylineLayer->ClearAllChains(); - polylineLayer->SetColor(0, 223, 21); + + const Color color(TOOL_LINES_COLOR_RED, + TOOL_LINES_COLOR_GREEN, + TOOL_LINES_COLOR_BLUE); { PolylineSceneLayer::Chain chain; chain.push_back(start_); chain.push_back(end_); - polylineLayer->AddChain(chain, false); + polylineLayer->AddChain(chain, false, color); } // handles { - //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength) - { PolylineSceneLayer::Chain chain; - AddSquare(chain, *GetScene(), start_, 10.0); //TODO: take DPI into account - polylineLayer->AddChain(chain, true); + + //TODO: take DPI into account + AddSquare(chain, GetScene(), start_, + GetController()->GetHandleSideLengthS()); + + polylineLayer->AddChain(chain, true, color); } { PolylineSceneLayer::Chain chain; - AddSquare(chain, *GetScene(), end_, 10.0); //TODO: take DPI into account - polylineLayer->AddChain(chain, true); + + //TODO: take DPI into account + AddSquare(chain, GetScene(), end_, + GetController()->GetHandleSideLengthS()); + + polylineLayer->AddChain(chain, true, color); } - - //ScenePoint2D startC = start_.Apply(GetScene()->GetSceneToCanvasTransform()); - //double squareSize = 10.0; - //double startHandleLX = startC.GetX() - squareSize/2; - //double startHandleTY = startC.GetY() - squareSize / 2; - //double startHandleRX = startC.GetX() + squareSize / 2; - //double startHandleBY = startC.GetY() + squareSize / 2; - //ScenePoint2D startLTC(startHandleLX, startHandleTY); - //ScenePoint2D startRTC(startHandleRX, startHandleTY); - //ScenePoint2D startRBC(startHandleRX, startHandleBY); - //ScenePoint2D startLBC(startHandleLX, startHandleBY); - - //ScenePoint2D startLT = startLTC.Apply(GetScene()->GetCanvasToSceneTransform()); - //ScenePoint2D startRT = startRTC.Apply(GetScene()->GetCanvasToSceneTransform()); - //ScenePoint2D startRB = startRBC.Apply(GetScene()->GetCanvasToSceneTransform()); - //ScenePoint2D startLB = startLBC.Apply(GetScene()->GetCanvasToSceneTransform()); - - //PolylineSceneLayer::Chain chain; - //chain.push_back(startLT); - //chain.push_back(startRT); - //chain.push_back(startRB); - //chain.push_back(startLB); - //polylineLayer->AddChain(chain, true); } } { - // Set the text layer proporeties - - TextSceneLayer* textLayer = GetTextLayer(); + // Set the text layer propreties double deltaX = end_.GetX() - start_.GetX(); double deltaY = end_.GetY() - start_.GetY(); double squareDist = deltaX * deltaX + deltaY * deltaY; double dist = sqrt(squareDist); char buf[64]; sprintf(buf, "%0.02f units", dist); - textLayer->SetText(buf); - textLayer->SetColor(0, 223, 21); // TODO: for now we simply position the text overlay at the middle // of the measuring segment double midX = 0.5 * (end_.GetX() + start_.GetX()); double midY = 0.5 * (end_.GetY() + start_.GetY()); - textLayer->SetPosition(midX, midY); + + SetTextLayerOutlineProperties( + GetScene(), layerHolder_, buf, ScenePoint2D(midX, midY)); } } else { - if (layersCreated) - { - RemoveFromScene(); - layersCreated = false; - } + RemoveFromScene(); } } } -} \ No newline at end of file +}
--- a/Framework/Scene2DViewport/LineMeasureTool.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/LineMeasureTool.h Mon Jun 24 14:35:00 2019 +0200 @@ -20,12 +20,11 @@ #pragma once -#include "MeasureTools.h" - -#include <Framework/Scene2D/Scene2D.h> -#include <Framework/Scene2D/ScenePoint2D.h> -#include <Framework/Scene2D/PolylineSceneLayer.h> -#include <Framework/Scene2D/TextSceneLayer.h> +#include "../Scene2D/PolylineSceneLayer.h" +#include "../Scene2D/Scene2D.h" +#include "../Scene2D/ScenePoint2D.h" +#include "../Scene2D/TextSceneLayer.h" +#include "MeasureTool.h" #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> @@ -38,14 +37,7 @@ class LineMeasureTool : public MeasureTool { public: - LineMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW) - : MeasureTool(broker, controllerW) - , layersCreated(false) - , polylineZIndex_(-1) - , textZIndex_(-1) - { - - } + LineMeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW); ~LineMeasureTool(); @@ -53,18 +45,18 @@ void SetEnd(ScenePoint2D end); void Set(ScenePoint2D start, ScenePoint2D end); + + virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE; + private: - PolylineSceneLayer* GetPolylineLayer(); - TextSceneLayer* GetTextLayer(); virtual void RefreshScene() ORTHANC_OVERRIDE; void RemoveFromScene(); private: - ScenePoint2D start_; - ScenePoint2D end_; - bool layersCreated; - int polylineZIndex_; - int textZIndex_; + ScenePoint2D start_; + ScenePoint2D end_; + boost::shared_ptr<LayerHolder> layerHolder_; + int baseLayerIndex_; }; }
--- a/Framework/Scene2DViewport/MeasureCommands.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureCommands.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -28,15 +28,17 @@ void CreateMeasureCommand::Undo() { // simply disable the measure tool upon undo + GetMeasureTool()->Disable(); GetController()->RemoveMeasureTool(GetMeasureTool()); } void CreateMeasureCommand::Redo() { + GetMeasureTool()->Enable(); GetController()->AddMeasureTool(GetMeasureTool()); } - CreateMeasureCommand::CreateMeasureCommand(ViewportControllerWPtr controllerW) + CreateMeasureCommand::CreateMeasureCommand(boost::weak_ptr<ViewportController> controllerW) : TrackerCommand(controllerW) { @@ -50,7 +52,7 @@ CreateLineMeasureCommand::CreateLineMeasureCommand( MessageBroker& broker, - ViewportControllerWPtr controllerW, + boost::weak_ptr<ViewportController> controllerW, ScenePoint2D point) : CreateMeasureCommand(controllerW) , measureTool_( @@ -67,7 +69,7 @@ CreateAngleMeasureCommand::CreateAngleMeasureCommand( MessageBroker& broker, - ViewportControllerWPtr controllerW, + boost::weak_ptr<ViewportController> controllerW, ScenePoint2D point) : CreateMeasureCommand(controllerW) , measureTool_( @@ -91,9 +93,9 @@ measureTool_->SetSide2End(scenePos); } - ViewportControllerPtr TrackerCommand::GetController() + boost::shared_ptr<ViewportController> TrackerCommand::GetController() { - ViewportControllerPtr controller = controllerW_.lock(); + boost::shared_ptr<ViewportController> controller = controllerW_.lock(); assert(controller); // accessing dead object? return controller; }
--- a/Framework/Scene2DViewport/MeasureCommands.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureCommands.h Mon Jun 24 14:35:00 2019 +0200 @@ -19,22 +19,23 @@ **/ #pragma once -#include <Framework/Scene2D/Scene2D.h> -#include <boost/shared_ptr.hpp> -#include <boost/noncopyable.hpp> +#include "../Scene2D/Scene2D.h" // to be moved into Stone -#include "PointerTypes.h" -#include "MeasureTools.h" +#include "PredeclaredTypes.h" +#include "MeasureTool.h" #include "LineMeasureTool.h" #include "AngleMeasureTool.h" +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> + namespace OrthancStone { class TrackerCommand : public boost::noncopyable { public: - TrackerCommand(ViewportControllerWPtr controllerW) + TrackerCommand(boost::weak_ptr<ViewportController> controllerW) : controllerW_(controllerW) { @@ -43,20 +44,20 @@ virtual void Redo() = 0; protected: - ViewportControllerPtr GetController(); - ViewportControllerWPtr controllerW_; + boost::shared_ptr<ViewportController> GetController(); + boost::weak_ptr<ViewportController> controllerW_; }; class CreateMeasureCommand : public TrackerCommand { public: - CreateMeasureCommand(ViewportControllerWPtr controllerW); + CreateMeasureCommand(boost::weak_ptr<ViewportController> controllerW); ~CreateMeasureCommand(); virtual void Undo() ORTHANC_OVERRIDE; virtual void Redo() ORTHANC_OVERRIDE; private: /** Must be implemented by the subclasses that create the actual tool */ - virtual MeasureToolPtr GetMeasureTool() = 0; + virtual boost::shared_ptr<MeasureTool> GetMeasureTool() = 0; }; class CreateLineMeasureCommand : public CreateMeasureCommand @@ -64,18 +65,18 @@ public: CreateLineMeasureCommand( MessageBroker& broker, - ViewportControllerWPtr controllerW, + boost::weak_ptr<ViewportController> controllerW, ScenePoint2D point); // the starting position is set in the ctor void SetEnd(ScenePoint2D scenePos); private: - virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE + virtual boost::shared_ptr<MeasureTool> GetMeasureTool() ORTHANC_OVERRIDE { return measureTool_; } - LineMeasureToolPtr measureTool_; + boost::shared_ptr<LineMeasureTool> measureTool_; }; @@ -85,7 +86,7 @@ /** Ctor sets end of side 1*/ CreateAngleMeasureCommand( MessageBroker& broker, - ViewportControllerWPtr controllerW, + boost::weak_ptr<ViewportController> controllerW, ScenePoint2D point); /** This method sets center*/ @@ -95,11 +96,11 @@ void SetSide2End(ScenePoint2D scenePos); private: - virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE + virtual boost::shared_ptr<MeasureTool> GetMeasureTool() ORTHANC_OVERRIDE { return measureTool_; } - AngleMeasureToolPtr measureTool_; + boost::shared_ptr<AngleMeasureTool> measureTool_; }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/MeasureTool.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,115 @@ +/** + * 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 "MeasureTool.h" + +#include <Core/Logging.h> +#include <Core/Enumerations.h> +#include <Core/OrthancException.h> + +#include <boost/math/constants/constants.hpp> + +namespace OrthancStone +{ + MeasureTool::~MeasureTool() + { + + } + + void MeasureTool::Enable() + { + enabled_ = true; + RefreshScene(); + } + + void MeasureTool::Disable() + { + enabled_ = false; + RefreshScene(); + } + + bool MeasureTool::IsEnabled() const + { + return enabled_; + } + + + boost::shared_ptr<const ViewportController> MeasureTool::GetController() const + { + boost::shared_ptr<const ViewportController> controller = controllerW_.lock(); + if (!controller) + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Using dead ViewportController object!"); + return controller; + } + + boost::shared_ptr<ViewportController> MeasureTool::GetController() + { +#if 1 + return boost::const_pointer_cast<ViewportController> + (const_cast<const MeasureTool*>(this)->GetController()); + //return boost::const_<boost::shared_ptr<ViewportController>> + // (const_cast<const MeasureTool*>(this)->GetController()); +#else + boost::shared_ptr<ViewportController> controller = controllerW_.lock(); + if (!controller) + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Using dead ViewportController object!"); + return controller; +#endif + } + + boost::shared_ptr<Scene2D> MeasureTool::GetScene() + { + return GetController()->GetScene(); + } + + boost::shared_ptr<const Scene2D> MeasureTool::GetScene() const + { + return GetController()->GetScene(); + } + + MeasureTool::MeasureTool(MessageBroker& broker, + boost::weak_ptr<ViewportController> controllerW) + : IObserver(broker) + , controllerW_(controllerW) + , enabled_(true) + { + GetController()->RegisterObserverCallback( + new Callable<MeasureTool, ViewportController::SceneTransformChanged> + (*this, &MeasureTool::OnSceneTransformChanged)); + } + + + bool MeasureTool::IsSceneAlive() const + { + boost::shared_ptr<ViewportController> controller = controllerW_.lock(); + return (controller.get() != NULL); + } + + void MeasureTool::OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message) + { + RefreshScene(); + } + + +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/MeasureTool.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,110 @@ +/** + * 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 "../Scene2D/PolylineSceneLayer.h" +#include "../Scene2D/Scene2D.h" +#include "../Scene2D/ScenePoint2D.h" +#include "../Scene2D/TextSceneLayer.h" +#include "../Scene2DViewport/PredeclaredTypes.h" +#include "../Scene2DViewport/ViewportController.h" + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> + +#include <vector> +#include <cmath> + +namespace OrthancStone +{ + class MeasureTool : public IObserver + { + public: + virtual ~MeasureTool(); + + /** + Enabled tools are rendered in the scene. + */ + void Enable(); + + /** + Disabled tools are not rendered in the scene. This is useful to be able + to use them as their own memento in command stacks (when a measure tool + creation command has been undone, the measure remains alive in the + command object but is disabled so that it can be redone later on easily) + */ + void Disable(); + + /** + This method is called when the scene transform changes. It allows to + recompute the visual elements whose content depend upon the scene transform + */ + void OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message); + + /** + This function must be implemented by the measuring tool to return whether + a given point in scene coords is close to the measuring tool. + + This is used for mouse hover highlighting. + + It is assumed that if the pointer position leads to this function returning + true, then a click at that position will return a tracker to edit the + measuring tool + */ + virtual bool HitTest(ScenePoint2D p) const = 0; + protected: + MeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW); + + /** + The measuring tool may exist in a standalone fashion, without any available + scene (because the controller is dead or dying). This call allows to check + before accessing the scene. + */ + bool IsSceneAlive() const; + + /** + This is the meat of the tool: this method must [create (if needed) and] + update the layers and their data according to the measure tool kind and + current state. This is repeatedly called during user interaction + */ + virtual void RefreshScene() = 0; + + boost::shared_ptr<const ViewportController> GetController() const; + boost::shared_ptr<ViewportController> GetController(); + + boost::shared_ptr<const Scene2D> GetScene() const; + boost::shared_ptr<Scene2D> GetScene(); + + /** + enabled_ is not accessible by subclasses because there is a state machine + that we do not wanna mess with + */ + bool IsEnabled() const; + + private: + boost::weak_ptr<ViewportController> controllerW_; + bool enabled_; + }; +} + +extern void TrackerSample_SetInfoDisplayMessage( + std::string key, std::string value);
--- a/Framework/Scene2DViewport/MeasureTools.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +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 "MeasureTools.h" - -#include <Core/Logging.h> -#include <Core/Enumerations.h> -#include <Core/OrthancException.h> - -#include <boost/math/constants/constants.hpp> - -using namespace Orthanc; - -namespace OrthancStone -{ - - MeasureTool::~MeasureTool() - { - - } - - void MeasureTool::Enable() - { - enabled_ = true; - RefreshScene(); - } - - void MeasureTool::Disable() - { - enabled_ = false; - RefreshScene(); - } - - bool MeasureTool::IsEnabled() const - { - return enabled_; - } - - - ViewportControllerPtr MeasureTool::GetController() - { - ViewportControllerPtr controller = controllerW_.lock(); - if (!controller) - throw OrthancException(ErrorCode_InternalError, - "Using dead ViewportController object!"); - return controller; - } - - OrthancStone::Scene2DPtr MeasureTool::GetScene() - { - return GetController()->GetScene(); - } - - MeasureTool::MeasureTool(MessageBroker& broker, - ViewportControllerWPtr controllerW) - : IObserver(broker) - , controllerW_(controllerW) - , enabled_(true) - { - GetController()->RegisterObserverCallback( - new Callable<MeasureTool, ViewportController::SceneTransformChanged> - (*this, &MeasureTool::OnSceneTransformChanged)); - } - - - bool MeasureTool::IsSceneAlive() const - { - ViewportControllerPtr controller = controllerW_.lock(); - return (controller.get() != NULL); - } - - void MeasureTool::OnSceneTransformChanged( - const ViewportController::SceneTransformChanged& message) - { - RefreshScene(); - } - - -} -
--- a/Framework/Scene2DViewport/MeasureTools.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +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 <Framework/Scene2DViewport/PointerTypes.h> -#include <Framework/Scene2DViewport/ViewportController.h> - -#include <Framework/Scene2D/Scene2D.h> -#include <Framework/Scene2D/ScenePoint2D.h> -#include <Framework/Scene2D/PolylineSceneLayer.h> -#include <Framework/Scene2D/TextSceneLayer.h> - -#include <boost/shared_ptr.hpp> -#include <boost/weak_ptr.hpp> - -#include <vector> -#include <cmath> - -namespace OrthancStone -{ - class MeasureTool : public IObserver - { - public: - virtual ~MeasureTool(); - - /** - Enabled tools are rendered in the scene. - */ - void Enable(); - - /** - Disabled tools are not rendered in the scene. This is useful to be able - to use them as their own memento in command stacks (when a measure tool - creation command has been undone, the measure remains alive in the - command object but is disabled so that it can be redone later on easily) - */ - void Disable(); - - /** - This method is called when the scene transform changes. It allows to - recompute the visual elements whose content depend upon the scene transform - */ - void OnSceneTransformChanged( - const ViewportController::SceneTransformChanged& message); - - protected: - MeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW); - - /** - The measuring tool may exist in a standalone fashion, without any available - scene (because the controller is dead or dying). This call allows to check - before accessing the scene. - */ - bool IsSceneAlive() const; - - /** - This is the meat of the tool: this method must [create (if needed) and] - update the layers and their data according to the measure tool kind and - current state. This is repeatedly called during user interaction - */ - virtual void RefreshScene() = 0; - - ViewportControllerPtr GetController(); - Scene2DPtr GetScene(); - - /** - enabled_ is not accessible by subclasses because there is a state machine - that we do not wanna mess with - */ - bool IsEnabled() const; - - private: - ViewportControllerWPtr controllerW_; - bool enabled_; - }; -} - - -extern void TrackerSample_SetInfoDisplayMessage( - std::string key, std::string value);
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureToolsToolbox.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -19,8 +19,12 @@ **/ #include "MeasureToolsToolbox.h" +#include "PredeclaredTypes.h" +#include "LayerHolder.h" +#include "ViewportController.h" -#include <Framework/Scene2D/TextSceneLayer.h> +#include "../Scene2D/TextSceneLayer.h" +#include "../Scene2D/Scene2D.h" #include <boost/math/constants/constants.hpp> @@ -31,6 +35,24 @@ namespace OrthancStone { + void GetPositionOnBisectingLine( + ScenePoint2D& result + , const ScenePoint2D& p1 + , const ScenePoint2D& c + , const ScenePoint2D& p2 + , const double d) + { + // TODO: fix correct half-plane + double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX()); + double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX()); + double angle = 0.5 * (p1cAngle + p2cAngle); + double unitVectorX = cos(angle); + double unitVectorY = sin(angle); + double posX = c.GetX() + d * unitVectorX; + double posY = c.GetX() + d * unitVectorY; + result = ScenePoint2D(posX, posY); + } + double RadiansToDegrees(double angleRad) { static const double factor = 180.0 / g_pi; @@ -38,27 +60,31 @@ } void AddSquare(PolylineSceneLayer::Chain& chain, - const Scene2D& scene, + boost::shared_ptr<const Scene2D> scene, const ScenePoint2D& centerS, - const double& sideLength) + const double& sideLengthS) { + // get the scaling factor + const double sceneToCanvas = + scene->GetSceneToCanvasTransform().ComputeZoom(); + chain.clear(); chain.reserve(4); - ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform()); + ScenePoint2D centerC = centerS.Apply(scene->GetSceneToCanvasTransform()); //TODO: take DPI into account - double handleLX = centerC.GetX() - sideLength / 2; - double handleTY = centerC.GetY() - sideLength / 2; - double handleRX = centerC.GetX() + sideLength / 2; - double handleBY = centerC.GetY() + sideLength / 2; + double handleLX = centerC.GetX() - sideLengthS * sceneToCanvas * 0.5; + double handleTY = centerC.GetY() - sideLengthS * sceneToCanvas * 0.5; + double handleRX = centerC.GetX() + sideLengthS * sceneToCanvas * 0.5; + double handleBY = centerC.GetY() + sideLengthS * sceneToCanvas * 0.5; ScenePoint2D LTC(handleLX, handleTY); ScenePoint2D RTC(handleRX, handleTY); ScenePoint2D RBC(handleRX, handleBY); ScenePoint2D LBC(handleLX, handleBY); - ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform()); - ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform()); - ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform()); - ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform()); + ScenePoint2D startLT = LTC.Apply(scene->GetCanvasToSceneTransform()); + ScenePoint2D startRT = RTC.Apply(scene->GetCanvasToSceneTransform()); + ScenePoint2D startRB = RBC.Apply(scene->GetCanvasToSceneTransform()); + ScenePoint2D startLB = LBC.Apply(scene->GetCanvasToSceneTransform()); chain.push_back(startLT); chain.push_back(startRT); @@ -86,7 +112,6 @@ void AddShortestArc( PolylineSceneLayer::Chain& chain - , const Scene2D& scene , const ScenePoint2D& p1 , const ScenePoint2D& c , const ScenePoint2D& p2 @@ -96,30 +121,11 @@ double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX()); double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX()); AddShortestArc( - chain, scene, c, radiusS, p1cAngle, p2cAngle, subdivisionsCount); + chain, c, radiusS, p1cAngle, p2cAngle, subdivisionsCount); } - void GetPositionOnBisectingLine( - ScenePoint2D& result - , const ScenePoint2D& p1 - , const ScenePoint2D& c - , const ScenePoint2D& p2 - , const double d) - { - // TODO: fix correct half-plane - double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX()); - double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX()); - double angle = 0.5*(p1cAngle + p2cAngle); - double unitVectorX = cos(angle); - double unitVectorY = sin(angle); - double posX = c.GetX() + d * unitVectorX; - double posY = c.GetX() + d * unitVectorY; - result = ScenePoint2D(posX, posY); - } - void AddShortestArc( PolylineSceneLayer::Chain& chain - , const Scene2D& scene , const ScenePoint2D& centerS , const double& radiusS , const double startAngleRad @@ -197,7 +203,6 @@ #endif void AddCircle(PolylineSceneLayer::Chain& chain, - const Scene2D& scene, const ScenePoint2D& centerS, const double& radiusS, const int numSubdivisions) @@ -277,60 +282,44 @@ } #endif - - namespace - { - /** - Helper function for outlined text rendering - */ - TextSceneLayer* GetOutlineTextLayer( - Scene2D& scene, int baseLayerIndex, int index) - { - assert(scene.HasLayer(baseLayerIndex)); - assert(index >= 0); - assert(index < 5); - - ISceneLayer * layer = &(scene.GetLayer(baseLayerIndex + index)); - TextSceneLayer * concreteLayer = dynamic_cast<TextSceneLayer*>(layer); - assert(concreteLayer != NULL); - return concreteLayer; - } - } - + /** + This utility function assumes that the layer holder contains 5 text layers + and will use the first four ones for the text background and the fifth one + for the actual text + */ void SetTextLayerOutlineProperties( - Scene2D& scene, int baseLayerIndex, const char* text, ScenePoint2D p) + boost::shared_ptr<Scene2D> scene, boost::shared_ptr<LayerHolder> layerHolder, + const char* text, ScenePoint2D p) { double xoffsets[5] = { 2, 0, -2, 0, 0 }; double yoffsets[5] = { 0, -2, 0, 2, 0 }; // get the scaling factor const double pixelToScene = - scene.GetCanvasToSceneTransform().ComputeZoom(); + scene->GetCanvasToSceneTransform().ComputeZoom(); for (int i = 0; i < 5; ++i) { - TextSceneLayer* textLayer = GetOutlineTextLayer(scene, baseLayerIndex, i); + TextSceneLayer* textLayer = layerHolder->GetTextLayer(i); textLayer->SetText(text); if (i == 4) - textLayer->SetColor(0, 223, 81); + { + textLayer->SetColor(TEXT_COLOR_RED, + TEXT_COLOR_GREEN, + TEXT_COLOR_BLUE); + } else - textLayer->SetColor(0, 56, 21); + { + textLayer->SetColor(TEXT_OUTLINE_COLOR_RED, + TEXT_OUTLINE_COLOR_GREEN, + TEXT_OUTLINE_COLOR_BLUE); + } ScenePoint2D textAnchor; - //GetPositionOnBisectingLine( - // textAnchor, side1End_, center_, side2End_, 40.0*pixelToScene); textLayer->SetPosition( p.GetX() + xoffsets[i] * pixelToScene, p.GetY() + yoffsets[i] * pixelToScene); } } - - - - - - - - }
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureToolsToolbox.h Mon Jun 24 14:35:00 2019 +0200 @@ -18,8 +18,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ -#include <Framework/Scene2D/PolylineSceneLayer.h> -#include <Framework/Scene2D/Scene2D.h> +#include "PredeclaredTypes.h" +#include "../Scene2D/PolylineSceneLayer.h" +#include "../Scene2D/Scene2D.h" namespace OrthancStone { @@ -30,10 +31,9 @@ square sides are parallel to the canvas boundaries. */ void AddSquare(PolylineSceneLayer::Chain& chain, - const Scene2D& scene, + boost::shared_ptr<const Scene2D> scene, const ScenePoint2D& centerS, - const double& sideLength); - + const double& sideLengthS); /** Creates an arc centered on c that goes @@ -49,7 +49,6 @@ */ void AddShortestArc( PolylineSceneLayer::Chain& chain - , const Scene2D& scene , const ScenePoint2D& p1 , const ScenePoint2D& c , const ScenePoint2D& p2 @@ -64,7 +63,6 @@ */ void AddShortestArc( PolylineSceneLayer::Chain& chain - , const Scene2D& scene , const ScenePoint2D& centerS , const double& radiusS , const double startAngleRad @@ -182,6 +180,6 @@ from layerIndex, up to (and not including) layerIndex+5. */ void SetTextLayerOutlineProperties( - Scene2D& scene, int baseLayerIndex, const char* text, ScenePoint2D p); - + boost::shared_ptr<Scene2D> scene, boost::shared_ptr<LayerHolder> layerHolder, + const char* text, ScenePoint2D p); }
--- a/Framework/Scene2DViewport/MeasureTrackers.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureTrackers.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -21,12 +21,10 @@ #include "MeasureTrackers.h" #include <Core/OrthancException.h> -using namespace Orthanc; - namespace OrthancStone { - CreateMeasureTracker::CreateMeasureTracker(ViewportControllerWPtr controllerW) + CreateMeasureTracker::CreateMeasureTracker(boost::weak_ptr<ViewportController> controllerW) : controllerW_(controllerW) , alive_(true) , commitResult_(true) @@ -56,7 +54,7 @@ command_->Undo(); } - OrthancStone::Scene2DPtr CreateMeasureTracker::GetScene() + boost::shared_ptr<Scene2D> CreateMeasureTracker::GetScene() { return controllerW_.lock()->GetScene(); }
--- a/Framework/Scene2DViewport/MeasureTrackers.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/MeasureTrackers.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,10 +21,10 @@ #pragma once #include "IFlexiblePointerTracker.h" -#include "../../Framework/Scene2D/Scene2D.h" -#include "../../Framework/Scene2D/PointerEvent.h" +#include "../Scene2D/Scene2D.h" +#include "../Scene2D/PointerEvent.h" -#include "MeasureTools.h" +#include "MeasureTool.h" #include "MeasureCommands.h" #include <vector> @@ -37,15 +37,15 @@ virtual void Cancel() ORTHANC_OVERRIDE; virtual bool IsAlive() const ORTHANC_OVERRIDE; protected: - CreateMeasureTracker(ViewportControllerWPtr controllerW); + CreateMeasureTracker(boost::weak_ptr<ViewportController> controllerW); ~CreateMeasureTracker(); protected: - CreateMeasureCommandPtr command_; - ViewportControllerWPtr controllerW_; + boost::shared_ptr<CreateMeasureCommand> command_; + boost::weak_ptr<ViewportController> controllerW_; bool alive_; - Scene2DPtr GetScene(); + boost::shared_ptr<Scene2D> GetScene(); private: bool commitResult_;
--- a/Framework/Scene2DViewport/OneGesturePointerTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/OneGesturePointerTracker.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -23,14 +23,12 @@ #include <Core/OrthancException.h> -#include <Framework/StoneException.h> - -using namespace Orthanc; +#include "../StoneException.h" namespace OrthancStone { OneGesturePointerTracker::OneGesturePointerTracker( - ViewportControllerWPtr controllerW) + boost::weak_ptr<ViewportController> controllerW) : controllerW_(controllerW) , alive_(true) , currentTouchCount_(1) @@ -64,7 +62,7 @@ return alive_; } - ViewportControllerPtr OneGesturePointerTracker::GetController() + boost::shared_ptr<ViewportController> OneGesturePointerTracker::GetController() { return controllerW_.lock(); }
--- a/Framework/Scene2DViewport/OneGesturePointerTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/OneGesturePointerTracker.h Mon Jun 24 14:35:00 2019 +0200 @@ -39,16 +39,16 @@ class OneGesturePointerTracker : public IFlexiblePointerTracker { public: - OneGesturePointerTracker(ViewportControllerWPtr controllerW); + OneGesturePointerTracker(boost::weak_ptr<ViewportController> controllerW); virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE; virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE; virtual bool IsAlive() const ORTHANC_OVERRIDE; protected: - ViewportControllerPtr GetController(); + boost::shared_ptr<ViewportController> GetController(); private: - ViewportControllerWPtr controllerW_; + boost::weak_ptr<ViewportController> controllerW_; bool alive_; int currentTouchCount_; };
--- a/Framework/Scene2DViewport/PointerTypes.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -/** - * 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 <boost/shared_ptr.hpp> -#include <boost/weak_ptr.hpp> - -#include <vector> - -namespace OrthancStone -{ - class Scene2D; - typedef boost::shared_ptr<Scene2D> Scene2DPtr; - - typedef boost::weak_ptr<Scene2D> Scene2DWPtr; - - class MeasureTool; - typedef boost::shared_ptr<MeasureTool> - MeasureToolPtr; - typedef boost::weak_ptr<MeasureTool> - MeasureToolWPtr; - - class LineMeasureTool; - typedef boost::shared_ptr<LineMeasureTool> - LineMeasureToolPtr; - - class AngleMeasureTool; - typedef boost::shared_ptr<AngleMeasureTool> - AngleMeasureToolPtr; - - class IPointerTracker; - typedef boost::shared_ptr<IPointerTracker> - PointerTrackerPtr; - - class IFlexiblePointerTracker; - typedef boost::shared_ptr<IFlexiblePointerTracker> - FlexiblePointerTrackerPtr; - - typedef boost::shared_ptr<LineMeasureTool> - LineMeasureToolPtr; - - class CreateMeasureCommand; - typedef boost::shared_ptr<CreateMeasureCommand> - CreateMeasureCommandPtr; - - class CreateLineMeasureCommand; - typedef boost::shared_ptr<CreateLineMeasureCommand> - CreateLineMeasureCommandPtr; - - class CreateAngleMeasureCommand; - typedef boost::shared_ptr<CreateAngleMeasureCommand> - CreateAngleMeasureCommandPtr; - - - typedef boost::shared_ptr<Scene2D> Scene2DPtr; - - class TrackerCommand; - typedef boost::shared_ptr<TrackerCommand> TrackerCommandPtr; - - class ViewportController; - typedef boost::shared_ptr<ViewportController> ViewportControllerPtr; - typedef boost::weak_ptr<ViewportController> ViewportControllerWPtr; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/PredeclaredTypes.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,41 @@ +/** + * 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 <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> + +namespace OrthancStone + +{ + class Scene2D; + class MeasureTool; + class LineMeasureTool; + class AngleMeasureTool; + class IPointerTracker; + class IFlexiblePointerTracker; + class CreateMeasureCommand; + class CreateLineMeasureCommand; + class CreateAngleMeasureCommand; + class TrackerCommand; + class ViewportController; + class LayerHolder; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/UndoStack.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,68 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-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 "UndoStack.h" + +#include "MeasureCommands.h" + +#include "../StoneException.h" + +namespace OrthancStone +{ + UndoStack::UndoStack() : numAppliedCommands_(0) + {} + + void UndoStack::PushCommand(boost::shared_ptr<TrackerCommand> command) + { + commandStack_.erase( + commandStack_.begin() + numAppliedCommands_, + commandStack_.end()); + + ORTHANC_ASSERT(std::find(commandStack_.begin(), commandStack_.end(), command) + == commandStack_.end(), "Duplicate command"); + commandStack_.push_back(command); + numAppliedCommands_++; + } + + void UndoStack::Undo() + { + ORTHANC_ASSERT(CanUndo(), ""); + commandStack_[numAppliedCommands_ - 1]->Undo(); + numAppliedCommands_--; + } + + void UndoStack::Redo() + { + ORTHANC_ASSERT(CanRedo(), ""); + commandStack_[numAppliedCommands_]->Redo(); + numAppliedCommands_++; + } + + bool UndoStack::CanUndo() const + { + return numAppliedCommands_ > 0; + } + + bool UndoStack::CanRedo() const + { + return numAppliedCommands_ < commandStack_.size(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2DViewport/UndoStack.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,77 @@ +/** + * 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/shared_ptr.hpp> + +#include <vector> + +namespace OrthancStone +{ + class TrackerCommand; + + class UndoStack + { + public: + UndoStack(); + + /** + Stores a command : + - this first trims the undo stack to keep the first numAppliedCommands_ + - then it adds the supplied command at the top of the undo stack + + In other words, when a new command is pushed, all the undone (and not + redone) commands are removed. + */ + void PushCommand(boost::shared_ptr<TrackerCommand> command); + + /** + Undoes the command at the top of the undo stack, or throws if there is no + command to undo. + You can check "CanUndo" first to protect against extraneous redo. + */ + void Undo(); + + /** + Redoes the command that is just above the last applied command in the undo + stack or throws if there is no command to redo. + You can check "CanRedo" first to protect against extraneous redo. + */ + void Redo(); + + /** selfexpl */ + bool CanUndo() const; + + /** selfexpl */ + bool CanRedo() const; + + private: + std::vector<boost::shared_ptr<TrackerCommand> > commandStack_; + + /** + This is always between >= 0 and <= undoStack_.size() and gives the + position where the controller is in the undo stack. + - If numAppliedCommands_ > 0, one can undo + - If numAppliedCommands_ < numAppliedCommands_.size(), one can redo + */ + size_t numAppliedCommands_; + }; +}
--- a/Framework/Scene2DViewport/ViewportController.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/ViewportController.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -19,24 +19,65 @@ **/ #include "ViewportController.h" + +#include "UndoStack.h" #include "MeasureCommands.h" -#include <Framework/StoneException.h> +#include "../StoneException.h" #include <boost/make_shared.hpp> -using namespace Orthanc; - namespace OrthancStone { - ViewportController::ViewportController(MessageBroker& broker) + ViewportController::ViewportController(boost::weak_ptr<UndoStack> undoStackW, MessageBroker& broker) : IObservable(broker) - , numAppliedCommands_(0) + , undoStackW_(undoStackW) + , canvasToSceneFactor_(0.0) { scene_ = boost::make_shared<Scene2D>(); } - Scene2DPtr ViewportController::GetScene() + boost::shared_ptr<UndoStack> ViewportController::GetUndoStack() + { + return undoStackW_.lock(); + } + + boost::shared_ptr<const UndoStack> ViewportController::GetUndoStack() const + { + return undoStackW_.lock(); + } + + void ViewportController::PushCommand(boost::shared_ptr<TrackerCommand> command) + { + GetUndoStack()->PushCommand(command); + } + + void ViewportController::Undo() + { + GetUndoStack()->Undo(); + } + + void ViewportController::Redo() + { + GetUndoStack()->Redo(); + } + + bool ViewportController::CanUndo() const + { + return GetUndoStack()->CanUndo(); + } + + bool ViewportController::CanRedo() const + { + return GetUndoStack()->CanRedo(); + } + + boost::shared_ptr<const Scene2D> ViewportController::GetScene() const + { + return scene_; + } + + boost::shared_ptr<Scene2D> ViewportController::GetScene() { return scene_; } @@ -46,16 +87,16 @@ throw StoneException(ErrorCode_NotImplemented); } - std::vector<MeasureToolPtr> ViewportController::HitTestMeasureTools( + std::vector<boost::shared_ptr<MeasureTool> > ViewportController::HitTestMeasureTools( ScenePoint2D p) { - std::vector<MeasureToolPtr> ret; + std::vector<boost::shared_ptr<MeasureTool> > ret; - - //for (size_t i = 0; i < measureTools_.size(); ++i) - //{ - - //} + for (size_t i = 0; i < measureTools_.size(); ++i) + { + if (measureTools_[i]->HitTest(p)) + ret.push_back(measureTools_[i]); + } return ret; } @@ -74,6 +115,10 @@ { scene_->SetSceneToCanvasTransform(transform); BroadcastMessage(SceneTransformChanged(*this)); + + // update the canvas to scene factor + canvasToSceneFactor_ = 0.0; + canvasToSceneFactor_ = GetCanvasToSceneFactor(); } void ViewportController::FitContent( @@ -83,55 +128,51 @@ BroadcastMessage(SceneTransformChanged(*this)); } - void ViewportController::PushCommand(TrackerCommandPtr command) - { - commandStack_.erase( - commandStack_.begin() + numAppliedCommands_, - commandStack_.end()); - - ORTHANC_ASSERT(std::find(commandStack_.begin(), commandStack_.end(), command) - == commandStack_.end(), "Duplicate command"); - commandStack_.push_back(command); - numAppliedCommands_++; - } - - void ViewportController::Undo() - { - ORTHANC_ASSERT(CanUndo(), ""); - commandStack_[numAppliedCommands_-1]->Undo(); - numAppliedCommands_--; - } - - void ViewportController::Redo() - { - ORTHANC_ASSERT(CanRedo(), ""); - commandStack_[numAppliedCommands_]->Redo(); - numAppliedCommands_++; - } - - bool ViewportController::CanUndo() const - { - return numAppliedCommands_ > 0; - } - - bool ViewportController::CanRedo() const - { - return numAppliedCommands_ < commandStack_.size(); - } - - void ViewportController::AddMeasureTool(MeasureToolPtr measureTool) + void ViewportController::AddMeasureTool(boost::shared_ptr<MeasureTool> measureTool) { ORTHANC_ASSERT(std::find(measureTools_.begin(), measureTools_.end(), measureTool) == measureTools_.end(), "Duplicate measure tool"); measureTools_.push_back(measureTool); } - void ViewportController::RemoveMeasureTool(MeasureToolPtr measureTool) + void ViewportController::RemoveMeasureTool(boost::shared_ptr<MeasureTool> measureTool) { ORTHANC_ASSERT(std::find(measureTools_.begin(), measureTools_.end(), measureTool) != measureTools_.end(), "Measure tool not found"); - measureTools_.push_back(measureTool); + measureTools_.erase( + std::remove(measureTools_.begin(), measureTools_.end(), measureTool), + measureTools_.end()); + } + + + double ViewportController::GetCanvasToSceneFactor() const + { + if (canvasToSceneFactor_ == 0) + { + canvasToSceneFactor_ = + GetScene()->GetCanvasToSceneTransform().ComputeZoom(); + } + return canvasToSceneFactor_; } + double ViewportController::GetHandleSideLengthS() const + { + return HANDLE_SIDE_LENGTH_CANVAS_COORD * GetCanvasToSceneFactor(); + } + + double ViewportController::GetAngleToolArcRadiusS() const + { + return ARC_RADIUS_CANVAS_COORD * GetCanvasToSceneFactor(); + } + + double ViewportController::GetHitTestMaximumDistanceS() const + { + return HIT_TEST_MAX_DISTANCE_CANVAS_COORD * GetCanvasToSceneFactor(); + } + + double ViewportController::GetAngleTopTextLabelDistanceS() const + { + return TEXT_CENTER_DISTANCE_CANVAS_COORD * GetCanvasToSceneFactor(); + } }
--- a/Framework/Scene2DViewport/ViewportController.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Scene2DViewport/ViewportController.h Mon Jun 24 14:35:00 2019 +0200 @@ -20,16 +20,37 @@ #pragma once -#include "PointerTypes.h" +#include "PredeclaredTypes.h" -#include <Framework/Scene2D/Scene2D.h> -#include <Framework/Scene2D/PointerEvent.h> -#include <Framework/Scene2DViewport/IFlexiblePointerTracker.h> +#include "../Scene2D/Scene2D.h" +#include "../Scene2D/PointerEvent.h" +#include "../Scene2DViewport/IFlexiblePointerTracker.h" #include <stack> namespace OrthancStone { + class UndoStack; + + const double ARC_RADIUS_CANVAS_COORD = 30.0; + const double TEXT_CENTER_DISTANCE_CANVAS_COORD = 90; + + const double HANDLE_SIDE_LENGTH_CANVAS_COORD = 10.0; + const double HIT_TEST_MAX_DISTANCE_CANVAS_COORD = 15.0; + + const uint8_t TEXT_COLOR_RED = 0; + const uint8_t TEXT_COLOR_GREEN = 223; + const uint8_t TEXT_COLOR_BLUE = 81; + + const uint8_t TOOL_LINES_COLOR_RED = 0; + const uint8_t TOOL_LINES_COLOR_GREEN = 223; + const uint8_t TOOL_LINES_COLOR_BLUE = 21; + + + const uint8_t TEXT_OUTLINE_COLOR_RED = 0; + const uint8_t TEXT_OUTLINE_COLOR_GREEN = 56; + const uint8_t TEXT_OUTLINE_COLOR_BLUE = 21; + /** This object is responsible for hosting a scene, responding to messages from the model and updating the scene accordingly. @@ -48,9 +69,10 @@ ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, \ SceneTransformChanged, ViewportController); - ViewportController(MessageBroker& broker); + ViewportController(boost::weak_ptr<UndoStack> undoStackW, MessageBroker& broker); - Scene2DPtr GetScene(); + boost::shared_ptr<const Scene2D> GetScene() const; + boost::shared_ptr<Scene2D> GetScene(); /** This method is called by the GUI system and should update/delete the @@ -63,13 +85,13 @@ (in scene coords). A tracker can then be requested from the chosen measure tool, if needed */ - std::vector<MeasureToolPtr> HitTestMeasureTools(ScenePoint2D p); + std::vector<boost::shared_ptr<MeasureTool> > HitTestMeasureTools(ScenePoint2D p); /** With this method, the object takes ownership of the supplied tracker and updates it according to user interaction */ - void SetActiveTracker(FlexiblePointerTrackerPtr tracker); + void SetActiveTracker(boost::shared_ptr<IFlexiblePointerTracker> tracker); /** Forwarded to the underlying scene */ const AffineTransform2D& GetCanvasToSceneTransform() const; @@ -83,54 +105,68 @@ /** Forwarded to the underlying scene, and broadcasted to the observers */ void FitContent(unsigned int canvasWidth, unsigned int canvasHeight); - /** - Stores a command : - - this first trims the undo stack to keep the first numAppliedCommands_ - - then it adds the supplied command at the top of the undo stack + /** Adds a new measure tool */ + void AddMeasureTool(boost::shared_ptr<MeasureTool> measureTool); + + /** Removes a measure tool or throws if it cannot be found */ + void RemoveMeasureTool(boost::shared_ptr<MeasureTool> measureTool); - In other words, when a new command is pushed, all the undone (and not - redone) commands are removed. + /** + The square handle side length in *scene* coordinates */ - void PushCommand(TrackerCommandPtr command); + double GetHandleSideLengthS() const; + + /** + The angle measure too arc radius in *scene* coordinates + + Note: you might wonder why this is not part of the AngleMeasureTool itself, + but we prefer to put all such constants in the same location, to ease + */ + double GetAngleToolArcRadiusS() const; /** - Undoes the command at the top of the undo stack, or throws if there is no - command to undo. - You can check "CanUndo" first to protect against extraneous redo. + The hit test maximum distance in *scene* coordinates. + If a pointer event is less than GetHandleSideLengthS() to a GUI element, + the hit test for this GUI element is seen as true */ + double GetHitTestMaximumDistanceS() const; + + /** + Distance between the top of the angle measuring tool and the center of + the label showing the actual measure, in *scene* coordinates + */ + double GetAngleTopTextLabelDistanceS() const; + + + /** forwarded to the UndoStack */ + void PushCommand(boost::shared_ptr<TrackerCommand> command); + + /** forwarded to the UndoStack */ void Undo(); - /** - Redoes the command that is just above the last applied command in the undo - stack or throws if there is no command to redo. - You can check "CanRedo" first to protect against extraneous redo. - */ + /** forwarded to the UndoStack */ void Redo(); - /** selfexpl */ + /** forwarded to the UndoStack */ bool CanUndo() const; - /** selfexpl */ + /** forwarded to the UndoStack */ bool CanRedo() const; - /** Adds a new measure tool */ - void AddMeasureTool(MeasureToolPtr measureTool); - - /** Removes a measure tool or throws if it cannot be found */ - void RemoveMeasureTool(MeasureToolPtr measureTool); private: - std::vector<TrackerCommandPtr> commandStack_; + double GetCanvasToSceneFactor() const; + + boost::weak_ptr<UndoStack> undoStackW_; + + boost::shared_ptr<UndoStack> GetUndoStack(); + boost::shared_ptr<const UndoStack> GetUndoStack() const; + + std::vector<boost::shared_ptr<MeasureTool> > measureTools_; + boost::shared_ptr<Scene2D> scene_; + boost::shared_ptr<IFlexiblePointerTracker> tracker_; - /** - This is always between >= 0 and <= undoStack_.size() and gives the - position where the controller is in the undo stack. - - If numAppliedCommands_ > 0, one can undo - - If numAppliedCommands_ < numAppliedCommands_.size(), one can redo - */ - size_t numAppliedCommands_; - std::vector<MeasureToolPtr> measureTools_; - Scene2DPtr scene_; - FlexiblePointerTrackerPtr tracker_; + // this is cached + mutable double canvasToSceneFactor_; }; }
--- a/Framework/SmartLoader.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,291 +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 "SmartLoader.h" -#include "Layers/DicomSeriesVolumeSlicer.h" -#include "Messages/MessageForwarder.h" -#include "Core/Images/Image.h" -#include "Framework/Widgets/SliceViewerWidget.h" -#include "Framework/StoneException.h" -#include "Framework/Layers/FrameRenderer.h" -#include "Core/Logging.h" - -namespace Deprecated -{ - enum CachedSliceStatus - { - CachedSliceStatus_ScheduledToLoad, - CachedSliceStatus_GeometryLoaded, - CachedSliceStatus_ImageLoaded - }; - - class SmartLoader::CachedSlice : public IVolumeSlicer - { - public: - class RendererFactory : public LayerReadyMessage::IRendererFactory - { - private: - const CachedSlice& that_; - - public: - RendererFactory(const CachedSlice& that) : - that_(that) - { - } - - virtual ILayerRenderer* CreateRenderer() const - { - bool isFull = (that_.effectiveQuality_ == OrthancStone::SliceImageQuality_FullPng || - that_.effectiveQuality_ == OrthancStone::SliceImageQuality_FullPam); - - return FrameRenderer::CreateRenderer(*that_.image_, *that_.slice_, isFull); - } - }; - - unsigned int sliceIndex_; - std::auto_ptr<Slice> slice_; - boost::shared_ptr<Orthanc::ImageAccessor> image_; - OrthancStone::SliceImageQuality effectiveQuality_; - CachedSliceStatus status_; - - public: - CachedSlice(OrthancStone::MessageBroker& broker) : - IVolumeSlicer(broker) - { - } - - virtual ~CachedSlice() - { - } - - virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, - const OrthancStone::CoordinateSystem3D& viewportSlice) - { - // TODO: viewportSlice is not used !!!! - slice_->GetExtent(points); - return true; - } - - virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) - { - // TODO: viewportSlice is not used !!!! - - // it has already been loaded -> trigger the "layer ready" message immediately otherwise, do nothing now. The LayerReady will be triggered - // once the VolumeSlicer is ready - if (status_ == CachedSliceStatus_ImageLoaded) - { - LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is loaded): " << slice_->GetOrthancInstanceId(); - - RendererFactory factory(*this); - BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice_->GetGeometry())); - } - else - { - LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is not loaded yet): " << slice_->GetOrthancInstanceId(); - } - } - - CachedSlice* Clone() const - { - CachedSlice* output = new CachedSlice(GetBroker()); - output->sliceIndex_ = sliceIndex_; - output->slice_.reset(slice_->Clone()); - output->image_ = image_; - output->effectiveQuality_ = effectiveQuality_; - output->status_ = status_; - - return output; - } - - }; - - - SmartLoader::SmartLoader(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthancApiClient) : - IObservable(broker), - IObserver(broker), - imageQuality_(OrthancStone::SliceImageQuality_FullPam), - orthancApiClient_(orthancApiClient) - { - } - - void SmartLoader::SetFrameInWidget(SliceViewerWidget& sliceViewer, - size_t layerIndex, - const std::string& instanceId, - unsigned int frame) - { - // TODO: check if this frame has already been loaded or is already being loaded. - // - if already loaded: create a "clone" that will emit the GeometryReady/ImageReady messages "immediately" - // (it can not be immediate because Observers needs to register first and this is done after this method returns) - // - if currently loading, we need to return an object that will observe the existing VolumeSlicer and forward - // the messages to its observables - // in both cases, we must be carefull about objects lifecycle !!! - - std::auto_ptr<IVolumeSlicer> layerSource; - std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame); - SmartLoader::CachedSlice* cachedSlice = NULL; - - if (cachedSlices_.find(sliceKeyId) != cachedSlices_.end()) // && cachedSlices_[sliceKeyId]->status_ == CachedSliceStatus_Loaded) - { - layerSource.reset(cachedSlices_[sliceKeyId]->Clone()); - cachedSlice = dynamic_cast<SmartLoader::CachedSlice*>(layerSource.get()); - } - else - { - layerSource.reset(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); - dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady)); - dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame); - } - - // make sure that the widget registers the events before we trigger them - if (sliceViewer.GetLayerCount() == layerIndex) - { - sliceViewer.AddLayer(layerSource.release()); - } - else if (sliceViewer.GetLayerCount() > layerIndex) - { - sliceViewer.ReplaceLayer(layerIndex, layerSource.release()); - } - else - { - throw OrthancStone::StoneException(OrthancStone::ErrorCode_CanOnlyAddOneLayerAtATime); - } - - if (cachedSlice != NULL) - { - BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*cachedSlice)); - } - - } - - void SmartLoader::PreloadSlice(const std::string instanceId, - unsigned int frame) - { - // TODO: reactivate -> need to be able to ScheduleLayerLoading in IVolumeSlicer without calling ScheduleLayerCreation - return; - // TODO: check if it is already in the cache - - - - // create the slice in the cache with "empty" data - boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker())); - cachedSlice->slice_.reset(new Slice(instanceId, frame)); - cachedSlice->status_ = CachedSliceStatus_ScheduledToLoad; - std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame); - - LOG(WARNING) << "Will preload: " << sliceKeyId; - - cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice); - - std::auto_ptr<IVolumeSlicer> layerSource(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); - - dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady)); - dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame); - - // keep a ref to the VolumeSlicer until the slice is fully loaded and saved to cache - preloadingInstances_[sliceKeyId] = boost::shared_ptr<IVolumeSlicer>(layerSource.release()); - } - - -// void PreloadStudy(const std::string studyId) -// { -// /* TODO */ -// } - -// void PreloadSeries(const std::string seriesId) -// { -// /* TODO */ -// } - - - void SmartLoader::OnLayerGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message) - { - const DicomSeriesVolumeSlicer& source = - dynamic_cast<const DicomSeriesVolumeSlicer&>(message.GetOrigin()); - - // save/replace the slice in cache - const Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount() - std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + - boost::lexical_cast<std::string>(slice.GetFrame())); - - LOG(WARNING) << "Geometry ready: " << sliceKeyId; - - boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker())); - cachedSlice->slice_.reset(slice.Clone()); - cachedSlice->effectiveQuality_ = source.GetImageQuality(); - cachedSlice->status_ = CachedSliceStatus_GeometryLoaded; - - cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice); - - // re-emit original Layer message to observers - BroadcastMessage(message); - } - - - void SmartLoader::OnFrameReady(const DicomSeriesVolumeSlicer::FrameReadyMessage& message) - { - // save/replace the slice in cache - const Slice& slice = message.GetSlice(); - std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + - boost::lexical_cast<std::string>(slice.GetFrame())); - - LOG(WARNING) << "Image ready: " << sliceKeyId; - - boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker())); - cachedSlice->image_.reset(Orthanc::Image::Clone(message.GetFrame())); - cachedSlice->effectiveQuality_ = message.GetImageQuality(); - cachedSlice->slice_.reset(message.GetSlice().Clone()); - cachedSlice->status_ = CachedSliceStatus_ImageLoaded; - - cachedSlices_[sliceKeyId] = cachedSlice; - - // re-emit original Layer message to observers - BroadcastMessage(message); - } - - - void SmartLoader::OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message) - { - const DicomSeriesVolumeSlicer& source = - dynamic_cast<const DicomSeriesVolumeSlicer&>(message.GetOrigin()); - - const Slice& slice = source.GetSlice(0); // TODO handle GetSliceCount() ? - std::string sliceKeyId = (slice.GetOrthancInstanceId() + ":" + - boost::lexical_cast<std::string>(slice.GetFrame())); - - LOG(WARNING) << "Layer ready: " << sliceKeyId; - - // remove the slice from the preloading slices now that it has been fully loaded and it is referenced in the cache - if (preloadingInstances_.find(sliceKeyId) != preloadingInstances_.end()) - { - preloadingInstances_.erase(sliceKeyId); - } - - // re-emit original Layer message to observers - BroadcastMessage(message); - } -}
--- a/Framework/SmartLoader.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +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 "Layers/DicomSeriesVolumeSlicer.h" -#include "Messages/IObservable.h" -#include "Toolbox/OrthancApiClient.h" - -namespace Deprecated -{ - class SliceViewerWidget; - - class SmartLoader : public OrthancStone::IObservable, public OrthancStone::IObserver - { - class CachedSlice; - - protected: - typedef std::map<std::string, boost::shared_ptr<SmartLoader::CachedSlice> > CachedSlices; - CachedSlices cachedSlices_; - - typedef std::map<std::string, boost::shared_ptr<IVolumeSlicer> > PreloadingInstances; - PreloadingInstances preloadingInstances_; - - OrthancStone::SliceImageQuality imageQuality_; - OrthancApiClient& orthancApiClient_; - - public: - SmartLoader(OrthancStone::MessageBroker& broker, OrthancApiClient& orthancApiClient); // TODO: add maxPreloadStorageSizeInBytes - -// void PreloadStudy(const std::string studyId); -// void PreloadSeries(const std::string seriesId); - void PreloadSlice(const std::string instanceId, unsigned int frame); - - void SetImageQuality(OrthancStone::SliceImageQuality imageQuality) { imageQuality_ = imageQuality; } - - void SetFrameInWidget(SliceViewerWidget& sliceViewer, size_t layerIndex, const std::string& instanceId, unsigned int frame); - - void GetFirstInstanceIdForSeries(std::string& output, const std::string& seriesId); - - private: - void OnLayerGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message); - void OnFrameReady(const DicomSeriesVolumeSlicer::FrameReadyMessage& message); - void OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message); - - }; - -}
--- a/Framework/StoneEnumerations.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/StoneEnumerations.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,6 +23,22 @@ #include <string> + +namespace Deprecated +{ + enum SliceImageQuality + { + SliceImageQuality_FullPng, // smaller to transmit but longer to generate on Orthanc side (better choice when on low bandwidth) + SliceImageQuality_FullPam, // bigger to transmit but faster to generate on Orthanc side (better choice when on localhost or LAN) + SliceImageQuality_Jpeg50, + SliceImageQuality_Jpeg90, + SliceImageQuality_Jpeg95, + + SliceImageQuality_InternalRaw // downloads the raw pixels data as they are stored in the DICOM file (internal use only) + }; +} + + namespace OrthancStone { enum SliceOffsetMode @@ -85,17 +101,6 @@ KeyboardKeys_Down = 40 }; - enum SliceImageQuality - { - SliceImageQuality_FullPng, // smaller to transmit but longer to generate on Orthanc side (better choice when on low bandwidth) - SliceImageQuality_FullPam, // bigger to transmit but faster to generate on Orthanc side (better choice when on localhost or LAN) - SliceImageQuality_Jpeg50, - SliceImageQuality_Jpeg90, - SliceImageQuality_Jpeg95, - - SliceImageQuality_InternalRaw // downloads the raw pixels data as they are stored in the DICOM file (internal use only) - }; - enum SopClassUid { SopClassUid_Other, @@ -115,23 +120,6 @@ BitmapAnchor_TopRight }; - enum ControlPointType - { - ControlPoint_TopLeftCorner = 0, - ControlPoint_TopRightCorner = 1, - ControlPoint_BottomRightCorner = 2, - ControlPoint_BottomLeftCorner = 3 - }; - - enum PhotometricDisplayMode - { - PhotometricDisplayMode_Default, - - PhotometricDisplayMode_Monochrome1, - PhotometricDisplayMode_Monochrome2 - }; - - SopClassUid StringToSopClassUid(const std::string& source); void ComputeWindowing(float& targetCenter,
--- a/Framework/StoneException.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/StoneException.h Mon Jun 24 14:35:00 2019 +0200 @@ -115,11 +115,44 @@ // See https://isocpp.org/wiki/faq/misc-technical-issues#macros-with-multi-stmts // (or google "Multiple lines macro C++ faq lite" if link is dead) -#define ORTHANC_ASSERT(cond,streamChainMessage) \ +#define ORTHANC_ASSERT2(cond,streamChainMessage) \ if (!(cond)) { \ std::stringstream sst; \ sst << "Assertion failed. Condition = \"" #cond "\" Message = \"" << streamChainMessage << "\""; \ std::string sstr = sst.str(); \ - throw OrthancException(ErrorCode_InternalError,sstr.c_str()); \ + throw ::Orthanc::OrthancException(::Orthanc::ErrorCode_InternalError,sstr.c_str()); \ + } else (void)0 + +#define ORTHANC_ASSERT1(cond) \ + if (!(cond)) { \ + std::stringstream sst; \ + sst << "Assertion failed. Condition = \"" #cond "\""; \ + std::string sstr = sst.str(); \ + throw ::Orthanc::OrthancException(::Orthanc::ErrorCode_InternalError,sstr.c_str()); \ } else (void)0 +# define ORTHANC_EXPAND( x ) x +# define GET_ORTHANC_ASSERT(_1,_2,NAME,...) NAME +# define ORTHANC_ASSERT(...) ORTHANC_EXPAND(GET_ORTHANC_ASSERT(__VA_ARGS__, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(__VA_ARGS__)) + +/* +Explanation: + +ORTHANC_ASSERT(a) +ORTHANC_EXPAND(GET_ORTHANC_ASSERT(a, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(a)) +ORTHANC_EXPAND(ORTHANC_ASSERT1(a)) +ORTHANC_ASSERT1(a) + +ORTHANC_ASSERT(a,b) +ORTHANC_EXPAND(GET_ORTHANC_ASSERT(a, b, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(a,b)) +ORTHANC_EXPAND(ORTHANC_ASSERT2(a,b)) +ORTHANC_ASSERT2(a,b) + +Note: ORTHANC_EXPAND is required for some older compilers (MS v100 cl.exe ) +*/ + + + + + +
--- a/Framework/StoneInitialization.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/StoneInitialization.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -31,6 +31,10 @@ # include "../Applications/Sdl/SdlWindow.h" #endif +#if ORTHANC_ENABLE_CURL == 1 +#include <Core/HttpClient.h> +#endif + namespace OrthancStone { #if ORTHANC_ENABLE_LOGGING_PLUGIN == 1 @@ -48,6 +52,10 @@ #if ORTHANC_ENABLE_SDL == 1 OrthancStone::SdlWindow::GlobalInitialize(); #endif + +#if ORTHANC_ENABLE_CURL == 1 + Orthanc::HttpClient::GlobalInitialize(); +#endif } void StoneFinalize() @@ -56,6 +64,10 @@ OrthancStone::SdlWindow::GlobalFinalize(); #endif +#if ORTHANC_ENABLE_CURL == 1 + Orthanc::HttpClient::GlobalFinalize(); +#endif + Orthanc::Logging::Finalize(); } }
--- a/Framework/Toolbox/BaseWebService.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -/** - * 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 "BaseWebService.h" - -#include <Core/OrthancException.h> -#include "Framework/Messages/IObservable.h" -#include "Platforms/Generic/IOracleCommand.h" -#include <boost/shared_ptr.hpp> - -namespace Deprecated -{ - - - class BaseWebService::BaseWebServicePayload : public Orthanc::IDynamicObject - { - private: - std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage> > userSuccessHandler_; - std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> > userFailureHandler_; - std::auto_ptr< Orthanc::IDynamicObject> userPayload_; - - public: - BaseWebServicePayload(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* userSuccessHandler, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* userFailureHandler, - Orthanc::IDynamicObject* userPayload) : - userSuccessHandler_(userSuccessHandler), - userFailureHandler_(userFailureHandler), - userPayload_(userPayload) - { - } - - void HandleSuccess(const IWebService::HttpRequestSuccessMessage& message) const - { - if (userSuccessHandler_.get() != NULL) - { - // recreate a success message with the user payload - IWebService::HttpRequestSuccessMessage successMessage(message.GetUri(), - message.GetAnswer(), - message.GetAnswerSize(), - message.GetAnswerHttpHeaders(), - userPayload_.get()); - userSuccessHandler_->Apply(successMessage); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - void HandleFailure(const IWebService::HttpRequestErrorMessage& message) const - { - if (userFailureHandler_.get() != NULL) - { - // recreate a failure message with the user payload - IWebService::HttpRequestErrorMessage failureMessage(message.GetUri(), - userPayload_.get()); - - userFailureHandler_->Apply(failureMessage); - } - } - - }; - - - void BaseWebService::GetAsync(const std::string& uri, - const HttpHeaders& headers, - Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, - unsigned int timeoutInSeconds) - { - if (cache_.find(uri) == cache_.end()) - { - GetAsyncInternal(uri, headers, - new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered - new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestSuccessMessage> - (*this, &BaseWebService::CacheAndNotifyHttpSuccess), - new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestErrorMessage> - (*this, &BaseWebService::NotifyHttpError), - timeoutInSeconds); - } - else - { - // create a command and "post" it to the Oracle so it is executed and commited "later" - NotifyHttpSuccessLater(cache_[uri], payload, successCallback); - } - - } - - - - void BaseWebService::NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message) - { - if (message.HasPayload()) - { - dynamic_cast<const BaseWebServicePayload&>(message.GetPayload()).HandleSuccess(message); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - void BaseWebService::CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message) - { - cache_[message.GetUri()] = boost::shared_ptr<CachedHttpRequestSuccessMessage>(new CachedHttpRequestSuccessMessage(message)); - NotifyHttpSuccess(message); - } - - void BaseWebService::NotifyHttpError(const IWebService::HttpRequestErrorMessage& message) - { - if (message.HasPayload()) - { - dynamic_cast<const BaseWebServicePayload&>(message.GetPayload()).HandleFailure(message); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - - -}
--- a/Framework/Toolbox/BaseWebService.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -/** - * 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 "IWebService.h" - -#include <string> -#include <map> - -namespace Deprecated -{ - // This is an intermediate of IWebService that implements some caching on - // the HTTP GET requests - class BaseWebService : public IWebService, public OrthancStone::IObserver - { - public: - class CachedHttpRequestSuccessMessage - { - protected: - std::string uri_; - void* answer_; - size_t answerSize_; - IWebService::HttpHeaders answerHeaders_; - - public: - CachedHttpRequestSuccessMessage(const IWebService::HttpRequestSuccessMessage& message) : - uri_(message.GetUri()), - answerSize_(message.GetAnswerSize()), - answerHeaders_(message.GetAnswerHttpHeaders()) - { - answer_ = malloc(answerSize_); - memcpy(answer_, message.GetAnswer(), answerSize_); - } - - ~CachedHttpRequestSuccessMessage() - { - free(answer_); - } - - const std::string& GetUri() const - { - return uri_; - } - - const void* GetAnswer() const - { - return answer_; - } - - size_t GetAnswerSize() const - { - return answerSize_; - } - - const IWebService::HttpHeaders& GetAnswerHttpHeaders() const - { - return answerHeaders_; - } - - }; - protected: - class BaseWebServicePayload; - - bool cacheEnabled_; - std::map<std::string, boost::shared_ptr<CachedHttpRequestSuccessMessage> > cache_; // TODO: this is currently an infinite cache ! - - public: - - BaseWebService(OrthancStone::MessageBroker& broker) : - IWebService(broker), - IObserver(broker), - cacheEnabled_(true) - { - } - - virtual ~BaseWebService() - { - } - - virtual void EnableCache(bool enable) - { - cacheEnabled_ = enable; - } - - virtual void GetAsync(const std::string& uri, - const HttpHeaders& headers, - Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - unsigned int timeoutInSeconds = 60); - - protected: - virtual void GetAsyncInternal(const std::string& uri, - const HttpHeaders& headers, - Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - unsigned int timeoutInSeconds = 60) = 0; - - virtual void NotifyHttpSuccessLater(boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedHttpMessage, - Orthanc::IDynamicObject* payload, // takes ownership - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback) = 0; - - private: - void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message); - - void NotifyHttpError(const IWebService::HttpRequestErrorMessage& message); - - void CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message); - - }; -}
--- a/Framework/Toolbox/CoordinateSystem3D.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -143,6 +143,19 @@ } + void CoordinateSystem3D::SetOrigin(const Vector& origin) + { + if (origin.size() != 3) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + origin_ = origin; + } + } + + Vector CoordinateSystem3D::MapSliceToWorldCoordinates(double x, double y) const { @@ -189,9 +202,9 @@ } - bool CoordinateSystem3D::GetDistance(double& distance, - const CoordinateSystem3D& a, - const CoordinateSystem3D& b) + bool CoordinateSystem3D::ComputeDistance(double& distance, + const CoordinateSystem3D& a, + const CoordinateSystem3D& b) { bool opposite; // Ignored
--- a/Framework/Toolbox/CoordinateSystem3D.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.h Mon Jun 24 14:35:00 2019 +0200 @@ -86,6 +86,8 @@ return axisY_; } + void SetOrigin(const Vector& origin); + Vector MapSliceToWorldCoordinates(double x, double y) const; @@ -104,8 +106,8 @@ const Vector& direction) const; // Returns "false" is the two planes are not parallel - static bool GetDistance(double& distance, - const CoordinateSystem3D& a, - const CoordinateSystem3D& b); + static bool ComputeDistance(double& distance, + const CoordinateSystem3D& a, + const CoordinateSystem3D& b); }; }
--- a/Framework/Toolbox/DicomFrameConverter.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,282 +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 "DicomFrameConverter.h" - -#include "LinearAlgebra.h" - -#include <Core/Images/Image.h> -#include <Core/Images/ImageProcessing.h> -#include <Core/OrthancException.h> -#include <Core/Toolbox.h> - -namespace Deprecated -{ - static const Orthanc::DicomTag IMAGE_TAGS[] = - { - Orthanc::DICOM_TAG_BITS_STORED, - Orthanc::DICOM_TAG_DOSE_GRID_SCALING, - Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION, - Orthanc::DICOM_TAG_PIXEL_REPRESENTATION, - Orthanc::DICOM_TAG_RESCALE_INTERCEPT, - Orthanc::DICOM_TAG_RESCALE_SLOPE, - Orthanc::DICOM_TAG_WINDOW_CENTER, - Orthanc::DICOM_TAG_WINDOW_WIDTH - }; - - - void DicomFrameConverter::SetDefaultParameters() - { - isSigned_ = true; - isColor_ = false; - hasRescale_ = false; - rescaleIntercept_ = 0; - rescaleSlope_ = 1; - hasDefaultWindow_ = false; - defaultWindowCenter_ = 128; - defaultWindowWidth_ = 256; - expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; - } - - - void DicomFrameConverter::ReadParameters(const Orthanc::DicomMap& dicom) - { - SetDefaultParameters(); - - OrthancStone::Vector c, w; - if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && - OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && - c.size() > 0 && - w.size() > 0) - { - hasDefaultWindow_ = true; - defaultWindowCenter_ = static_cast<float>(c[0]); - defaultWindowWidth_ = static_cast<float>(w[0]); - } - - int32_t tmp; - if (!dicom.ParseInteger32(tmp, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION)) - { - // Type 1 tag, must be present - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - isSigned_ = (tmp == 1); - - double doseGridScaling; - bool isRTDose = false; - - if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && - dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) - { - hasRescale_ = true; - } - else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) - { - // This is for RT-DOSE - hasRescale_ = true; - isRTDose = true; - rescaleIntercept_ = 0; - rescaleSlope_ = doseGridScaling; - - if (!dicom.ParseInteger32(tmp, Orthanc::DICOM_TAG_BITS_STORED)) - { - // Type 1 tag, must be present - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - switch (tmp) - { - case 16: - expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; - break; - - case 32: - expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - std::string photometric; - if (dicom.CopyToString(photometric, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION, false)) - { - photometric = Orthanc::Toolbox::StripSpaces(photometric); - } - else - { - // Type 1 tag, must be present - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - photometric_ = Orthanc::StringToPhotometricInterpretation(photometric.c_str()); - - isColor_ = (photometric != "MONOCHROME1" && - photometric != "MONOCHROME2"); - - // TODO Add more checks, e.g. on the number of bytes per value - // (cf. DicomImageInformation.h in Orthanc) - - if (!isRTDose) - { - if (isColor_) - { - expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; - } - else if (isSigned_) - { - expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; - } - else - { - expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; - } - } - } - - - void DicomFrameConverter::ReadParameters(const OrthancPlugins::IDicomDataset& dicom) - { - Orthanc::DicomMap converted; - - for (size_t i = 0; i < sizeof(IMAGE_TAGS) / sizeof(Orthanc::DicomTag); i++) - { - OrthancPlugins::DicomTag tag(IMAGE_TAGS[i].GetGroup(), IMAGE_TAGS[i].GetElement()); - - std::string value; - if (dicom.GetStringValue(value, tag)) - { - converted.SetValue(IMAGE_TAGS[i], value, false); - } - } - - ReadParameters(converted); - } - - - void DicomFrameConverter::ConvertFrameInplace(std::auto_ptr<Orthanc::ImageAccessor>& source) const - { - assert(sizeof(float) == 4); - - if (source.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - if (source->GetFormat() == GetExpectedPixelFormat() && - source->GetFormat() == Orthanc::PixelFormat_RGB24) - { - // No conversion has to be done, check out (*) - return; - } - else - { - source.reset(ConvertFrame(*source)); - } - } - - - Orthanc::ImageAccessor* DicomFrameConverter::ConvertFrame(const Orthanc::ImageAccessor& source) const - { - assert(sizeof(float) == 4); - - Orthanc::PixelFormat sourceFormat = source.GetFormat(); - - if (sourceFormat != GetExpectedPixelFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - if (sourceFormat == Orthanc::PixelFormat_RGB24) - { - // This is the case of a color image. No conversion has to be done (*) - std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_RGB24, - source.GetWidth(), - source.GetHeight(), - false)); - Orthanc::ImageProcessing::Copy(*converted, source); - return converted.release(); - } - else - { - assert(sourceFormat == Orthanc::PixelFormat_Grayscale16 || - sourceFormat == Orthanc::PixelFormat_Grayscale32 || - sourceFormat == Orthanc::PixelFormat_SignedGrayscale16); - - // This is the case of a grayscale frame. Convert it to Float32. - std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, - source.GetWidth(), - source.GetHeight(), - false)); - Orthanc::ImageProcessing::Convert(*converted, source); - - // Correct rescale slope/intercept if need be - ApplyRescale(*converted, sourceFormat != Orthanc::PixelFormat_Grayscale32); - - return converted.release(); - } - } - - - void DicomFrameConverter::ApplyRescale(Orthanc::ImageAccessor& image, - bool useDouble) const - { - if (image.GetFormat() != Orthanc::PixelFormat_Float32) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - if (hasRescale_) - { - for (unsigned int y = 0; y < image.GetHeight(); y++) - { - float* p = reinterpret_cast<float*>(image.GetRow(y)); - - if (useDouble) - { - // Slower, accurate implementation using double - for (unsigned int x = 0; x < image.GetWidth(); x++, p++) - { - double value = static_cast<double>(*p); - *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_); - } - } - else - { - // Fast, approximate implementation using float - for (unsigned int x = 0; x < image.GetWidth(); x++, p++) - { - *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_); - } - } - } - } - } - - - double DicomFrameConverter::Apply(double x) const - { - return x * rescaleSlope_ + rescaleIntercept_; - } - -}
--- a/Framework/Toolbox/DicomFrameConverter.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +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 <Plugins/Samples/Common/IDicomDataset.h> -#include <Core/DicomFormat/DicomMap.h> -#include <Core/Images/ImageAccessor.h> - -#include <memory> - -namespace Deprecated -{ - /** - * This class is responsible for converting the pixel format of a - * DICOM frame coming from Orthanc, into a pixel format that is - * suitable for Stone, given the relevant DICOM tags: - * - Color frames will stay in the RGB24 format. - * - Grayscale frames will be converted to the Float32 format. - **/ - class DicomFrameConverter - { - private: - bool isSigned_; - bool isColor_; - bool hasRescale_; - double rescaleIntercept_; - double rescaleSlope_; - bool hasDefaultWindow_; - double defaultWindowCenter_; - double defaultWindowWidth_; - - Orthanc::PhotometricInterpretation photometric_; - Orthanc::PixelFormat expectedPixelFormat_; - - void SetDefaultParameters(); - - public: - DicomFrameConverter() - { - 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; - } - - // AM: this is required to serialize/deserialize it - DicomFrameConverter( - bool isSigned, - bool isColor, - bool hasRescale, - double rescaleIntercept, - double rescaleSlope, - bool hasDefaultWindow, - double defaultWindowCenter, - double defaultWindowWidth, - Orthanc::PhotometricInterpretation photometric, - Orthanc::PixelFormat expectedPixelFormat - ): - isSigned_(isSigned), - isColor_(isColor), - hasRescale_(hasRescale), - rescaleIntercept_(rescaleIntercept), - rescaleSlope_(rescaleSlope), - hasDefaultWindow_(hasDefaultWindow), - defaultWindowCenter_(defaultWindowCenter), - defaultWindowWidth_(defaultWindowWidth), - photometric_(photometric), - expectedPixelFormat_(expectedPixelFormat) - {} - - void GetParameters(bool& isSigned, - bool& isColor, - bool& hasRescale, - double& rescaleIntercept, - double& rescaleSlope, - bool& hasDefaultWindow, - double& defaultWindowCenter, - double& defaultWindowWidth, - Orthanc::PhotometricInterpretation& photometric, - Orthanc::PixelFormat& expectedPixelFormat) const - { - isSigned = isSigned_; - isColor = isColor_; - hasRescale = hasRescale_; - rescaleIntercept = rescaleIntercept_; - rescaleSlope = rescaleSlope_; - hasDefaultWindow = hasDefaultWindow_; - defaultWindowCenter = defaultWindowCenter_; - defaultWindowWidth = defaultWindowWidth_; - photometric = photometric_; - expectedPixelFormat = expectedPixelFormat_; - } - - Orthanc::PixelFormat GetExpectedPixelFormat() const - { - return expectedPixelFormat_; - } - - Orthanc::PhotometricInterpretation GetPhotometricInterpretation() const - { - return photometric_; - } - - void ReadParameters(const Orthanc::DicomMap& dicom); - - void ReadParameters(const OrthancPlugins::IDicomDataset& dicom); - - bool HasDefaultWindow() const - { - return hasDefaultWindow_; - } - - double GetDefaultWindowCenter() const - { - return defaultWindowCenter_; - } - - double GetDefaultWindowWidth() const - { - return defaultWindowWidth_; - } - - double GetRescaleIntercept() const - { - return rescaleIntercept_; - } - - double GetRescaleSlope() const - { - return rescaleSlope_; - } - - void ConvertFrameInplace(std::auto_ptr<Orthanc::ImageAccessor>& source) const; - - Orthanc::ImageAccessor* ConvertFrame(const Orthanc::ImageAccessor& source) const; - - void ApplyRescale(Orthanc::ImageAccessor& image, - bool useDouble) const; - - double Apply(double x) const; - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/DicomInstanceParameters.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,397 @@ +/** + * 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 "DicomInstanceParameters.h" + +#include "../Scene2D/ColorTextureSceneLayer.h" +#include "../Scene2D/FloatTextureSceneLayer.h" +#include "../Toolbox/GeometryToolbox.h" + +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Logging.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + + +namespace OrthancStone +{ + void DicomInstanceParameters::Data::ComputeDoseOffsets(const Orthanc::DicomMap& dicom) + { + // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html + + { + std::string increment; + + if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) + { + Orthanc::Toolbox::ToUpperCase(increment); + if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag + { + LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; + return; + } + } + } + + if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || + frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) + { + LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; + frameOffsets_.clear(); + } + else + { + if (frameOffsets_.size() >= 2) + { + thickness_ = std::abs(frameOffsets_[1] - frameOffsets_[0]); + } + } + } + + + DicomInstanceParameters::Data::Data(const Orthanc::DicomMap& dicom) : + imageInformation_(dicom) + { + if (imageInformation_.GetNumberOfFrames() <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || + !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) || + !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + std::string s; + if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + sopClassUid_ = StringToSopClassUid(s); + } + + if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) + { + thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); + } + + GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); + + std::string position, orientation; + if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && + dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) + { + geometry_ = CoordinateSystem3D(position, orientation); + } + + if (sopClassUid_ == SopClassUid_RTDose) + { + ComputeDoseOffsets(dicom); + } + + isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && + imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); + + double doseGridScaling; + + if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && + dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) + { + hasRescale_ = true; + } + else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) + { + hasRescale_ = true; + rescaleIntercept_ = 0; + rescaleSlope_ = doseGridScaling; + } + else + { + hasRescale_ = false; + } + + Vector c, w; + if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && + LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && + c.size() > 0 && + w.size() > 0) + { + hasDefaultWindowing_ = true; + defaultWindowingCenter_ = static_cast<float>(c[0]); + defaultWindowingWidth_ = static_cast<float>(w[0]); + } + else + { + hasDefaultWindowing_ = false; + } + + if (sopClassUid_ == SopClassUid_RTDose) + { + switch (imageInformation_.GetBitsStored()) + { + case 16: + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + break; + + case 32: + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + else if (isColor_) + { + expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; + } + else if (imageInformation_.IsSigned()) + { + expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; + } + else + { + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + } + } + + + CoordinateSystem3D DicomInstanceParameters::Data::GetFrameGeometry(unsigned int frame) const + { + if (frame == 0) + { + return geometry_; + } + else if (frame >= imageInformation_.GetNumberOfFrames()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else if (sopClassUid_ == SopClassUid_RTDose) + { + if (frame >= frameOffsets_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + return CoordinateSystem3D( + geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), + geometry_.GetAxisX(), + geometry_.GetAxisY()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + bool DicomInstanceParameters::Data::IsPlaneWithinSlice(unsigned int frame, + const CoordinateSystem3D& plane) const + { + if (frame >= imageInformation_.GetNumberOfFrames()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + CoordinateSystem3D tmp = geometry_; + + if (frame != 0) + { + tmp = GetFrameGeometry(frame); + } + + double distance; + + return (CoordinateSystem3D::ComputeDistance(distance, tmp, plane) && + distance <= thickness_ / 2.0); + } + + + void DicomInstanceParameters::Data::ApplyRescale(Orthanc::ImageAccessor& image, + bool useDouble) const + { + if (image.GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (hasRescale_) + { + const unsigned int width = image.GetWidth(); + const unsigned int height = image.GetHeight(); + + for (unsigned int y = 0; y < height; y++) + { + float* p = reinterpret_cast<float*>(image.GetRow(y)); + + if (useDouble) + { + // Slower, accurate implementation using double + for (unsigned int x = 0; x < width; x++, p++) + { + double value = static_cast<double>(*p); + *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_); + } + } + else + { + // Fast, approximate implementation using float + for (unsigned int x = 0; x < width; x++, p++) + { + *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_); + } + } + } + } + } + + double DicomInstanceParameters::GetRescaleIntercept() const + { + if (data_.hasRescale_) + { + return data_.rescaleIntercept_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + double DicomInstanceParameters::GetRescaleSlope() const + { + if (data_.hasRescale_) + { + return data_.rescaleSlope_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + float DicomInstanceParameters::GetDefaultWindowingCenter() const + { + if (data_.hasDefaultWindowing_) + { + return data_.defaultWindowingCenter_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + float DicomInstanceParameters::GetDefaultWindowingWidth() const + { + if (data_.hasDefaultWindowing_) + { + return data_.defaultWindowingWidth_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + Orthanc::ImageAccessor* DicomInstanceParameters::ConvertToFloat(const Orthanc::ImageAccessor& pixelData) const + { + std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, + pixelData.GetWidth(), + pixelData.GetHeight(), + false)); + Orthanc::ImageProcessing::Convert(*converted, pixelData); + + // Correct rescale slope/intercept if need be + data_.ApplyRescale(*converted, (pixelData.GetFormat() == Orthanc::PixelFormat_Grayscale32)); + + return converted.release(); + } + + + + TextureBaseSceneLayer* DicomInstanceParameters::CreateTexture + (const Orthanc::ImageAccessor& pixelData) const + { + assert(sizeof(float) == 4); + + Orthanc::PixelFormat sourceFormat = pixelData.GetFormat(); + + if (sourceFormat != GetExpectedPixelFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (sourceFormat == Orthanc::PixelFormat_RGB24) + { + // This is the case of a color image. No conversion has to be done. + return new ColorTextureSceneLayer(pixelData); + } + else + { + // This is the case of a grayscale frame. Convert it to Float32. + std::auto_ptr<FloatTextureSceneLayer> texture; + + if (pixelData.GetFormat() == Orthanc::PixelFormat_Float32) + { + texture.reset(new FloatTextureSceneLayer(pixelData)); + } + else + { + std::auto_ptr<Orthanc::ImageAccessor> converted(ConvertToFloat(pixelData)); + texture.reset(new FloatTextureSceneLayer(*converted)); + } + + if (data_.hasDefaultWindowing_) + { + texture->SetCustomWindowing(data_.defaultWindowingCenter_, + data_.defaultWindowingWidth_); + } + + return texture.release(); + } + } + + + LookupTableTextureSceneLayer* DicomInstanceParameters::CreateLookupTableTexture + (const Orthanc::ImageAccessor& pixelData) const + { + std::auto_ptr<FloatTextureSceneLayer> texture; + + if (pixelData.GetFormat() == Orthanc::PixelFormat_Float32) + { + return new LookupTableTextureSceneLayer(pixelData); + } + else + { + std::auto_ptr<Orthanc::ImageAccessor> converted(ConvertToFloat(pixelData)); + return new LookupTableTextureSceneLayer(*converted); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/DicomInstanceParameters.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,195 @@ +/** + * 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 "../StoneEnumerations.h" +#include "../Scene2D/LookupTableTextureSceneLayer.h" +#include "../Toolbox/CoordinateSystem3D.h" + +#include <Core/IDynamicObject.h> +#include <Core/DicomFormat/DicomImageInformation.h> + +namespace OrthancStone +{ + class DicomInstanceParameters : + public Orthanc::IDynamicObject /* to be used as a payload to SlicesSorter */ + { + // This class supersedes the deprecated "DicomFrameConverter" + + private: + struct Data // Struct to ease the copy constructor + { + std::string orthancInstanceId_; + std::string studyInstanceUid_; + std::string seriesInstanceUid_; + std::string sopInstanceUid_; + Orthanc::DicomImageInformation imageInformation_; + SopClassUid sopClassUid_; + double thickness_; + double pixelSpacingX_; + double pixelSpacingY_; + CoordinateSystem3D geometry_; + Vector frameOffsets_; + bool isColor_; + bool hasRescale_; + double rescaleIntercept_; + double rescaleSlope_; + bool hasDefaultWindowing_; + float defaultWindowingCenter_; + float defaultWindowingWidth_; + Orthanc::PixelFormat expectedPixelFormat_; + + void ComputeDoseOffsets(const Orthanc::DicomMap& dicom); + + Data(const Orthanc::DicomMap& dicom); + + CoordinateSystem3D GetFrameGeometry(unsigned int frame) const; + + bool IsPlaneWithinSlice(unsigned int frame, + const CoordinateSystem3D& plane) const; + + void ApplyRescale(Orthanc::ImageAccessor& image, + bool useDouble) const; + }; + + + Orthanc::ImageAccessor* ConvertToFloat(const Orthanc::ImageAccessor& pixelData) const; + + + Data data_; + + + public: + DicomInstanceParameters(const DicomInstanceParameters& other) : + data_(other.data_) + { + } + + DicomInstanceParameters(const Orthanc::DicomMap& dicom) : + data_(dicom) + { + } + + DicomInstanceParameters* Clone() const + { + return new DicomInstanceParameters(*this); + } + + void SetOrthancInstanceIdentifier(const std::string& id) + { + data_.orthancInstanceId_ = id; + } + + const std::string& GetOrthancInstanceIdentifier() const + { + return data_.orthancInstanceId_; + } + + const Orthanc::DicomImageInformation& GetImageInformation() const + { + return data_.imageInformation_; + } + + const std::string& GetStudyInstanceUid() const + { + return data_.studyInstanceUid_; + } + + const std::string& GetSeriesInstanceUid() const + { + return data_.seriesInstanceUid_; + } + + const std::string& GetSopInstanceUid() const + { + return data_.sopInstanceUid_; + } + + SopClassUid GetSopClassUid() const + { + return data_.sopClassUid_; + } + + double GetThickness() const + { + return data_.thickness_; + } + + double GetPixelSpacingX() const + { + return data_.pixelSpacingX_; + } + + double GetPixelSpacingY() const + { + return data_.pixelSpacingY_; + } + + const CoordinateSystem3D& GetGeometry() const + { + return data_.geometry_; + } + + CoordinateSystem3D GetFrameGeometry(unsigned int frame) const + { + return data_.GetFrameGeometry(frame); + } + + bool IsPlaneWithinSlice(unsigned int frame, + const CoordinateSystem3D& plane) const + { + return data_.IsPlaneWithinSlice(frame, plane); + } + + bool IsColor() const + { + return data_.isColor_; + } + + bool HasRescale() const + { + return data_.hasRescale_; + } + + double GetRescaleIntercept() const; + + double GetRescaleSlope() const; + + bool HasDefaultWindowing() const + { + return data_.hasDefaultWindowing_; + } + + float GetDefaultWindowingCenter() const; + + float GetDefaultWindowingWidth() const; + + Orthanc::PixelFormat GetExpectedPixelFormat() const + { + return data_.expectedPixelFormat_; + } + + TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& pixelData) const; + + LookupTableTextureSceneLayer* CreateLookupTableTexture(const Orthanc::ImageAccessor& pixelData) const; + }; +}
--- a/Framework/Toolbox/DicomStructureSet.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Toolbox/DicomStructureSet.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -22,7 +22,6 @@ #include "DicomStructureSet.h" #include "../Toolbox/GeometryToolbox.h" -#include "../Toolbox/MessagingToolbox.h" #include <Core/Logging.h> #include <Core/OrthancException.h> @@ -31,7 +30,6 @@ #include <limits> #include <stdio.h> -#include <boost/lexical_cast.hpp> #include <boost/geometry.hpp> #include <boost/geometry/geometries/point_xy.hpp> #include <boost/geometry/geometries/polygon.hpp> @@ -367,9 +365,7 @@ DicomStructureSet::DicomStructureSet(const OrthancPlugins::FullOrthancDataset& tags) { - using namespace OrthancPlugins; - - DicomDatasetReader reader(tags); + OrthancPlugins::DicomDatasetReader reader(tags); size_t count, tmp; if (!tags.GetSequenceSize(count, DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE) || @@ -385,18 +381,18 @@ for (size_t i = 0; i < count; i++) { structures_[i].interpretation_ = reader.GetStringValue - (DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i, - DICOM_TAG_RT_ROI_INTERPRETED_TYPE), + (OrthancPlugins::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i, + DICOM_TAG_RT_ROI_INTERPRETED_TYPE), "No interpretation"); structures_[i].name_ = reader.GetStringValue - (DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, - DICOM_TAG_ROI_NAME), + (OrthancPlugins::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, + DICOM_TAG_ROI_NAME), "No interpretation"); Vector color; - if (ParseVector(color, tags, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_ROI_DISPLAY_COLOR)) && + if (ParseVector(color, tags, OrthancPlugins::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_ROI_DISPLAY_COLOR)) && color.size() == 3) { structures_[i].red_ = ConvertColor(color[0]); @@ -411,37 +407,55 @@ } size_t countSlices; - if (!tags.GetSequenceSize(countSlices, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE))) + if (!tags.GetSequenceSize(countSlices, OrthancPlugins::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE))) { countSlices = 0; } - LOG(WARNING) << "New RT structure: \"" << structures_[i].name_ + LOG(INFO) << "New RT structure: \"" << structures_[i].name_ << "\" with interpretation \"" << structures_[i].interpretation_ << "\" containing " << countSlices << " slices (color: " << static_cast<int>(structures_[i].red_) << "," << static_cast<int>(structures_[i].green_) << "," << static_cast<int>(structures_[i].blue_) << ")"; + // These temporary variables avoid allocating many vectors in the loop below + OrthancPlugins::DicomPath countPointsPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, 0, + DICOM_TAG_NUMBER_OF_CONTOUR_POINTS); + + OrthancPlugins::DicomPath geometricTypePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, 0, + DICOM_TAG_CONTOUR_GEOMETRIC_TYPE); + + OrthancPlugins::DicomPath imageSequencePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, 0, + DICOM_TAG_CONTOUR_IMAGE_SEQUENCE); + + OrthancPlugins::DicomPath referencedInstancePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, 0, + DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, + DICOM_TAG_REFERENCED_SOP_INSTANCE_UID); + + OrthancPlugins::DicomPath contourDataPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, + DICOM_TAG_CONTOUR_SEQUENCE, 0, + DICOM_TAG_CONTOUR_DATA); + for (size_t j = 0; j < countSlices; j++) { unsigned int countPoints; - if (!reader.GetUnsignedIntegerValue - (countPoints, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, j, - DICOM_TAG_NUMBER_OF_CONTOUR_POINTS))) + countPointsPath.SetPrefixIndex(1, j); + if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath)) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices"; - std::string type = reader.GetMandatoryStringValue - (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, j, - DICOM_TAG_CONTOUR_GEOMETRIC_TYPE)); + geometricTypePath.SetPrefixIndex(1, j); + std::string type = reader.GetMandatoryStringValue(geometricTypePath); if (type != "CLOSED_PLANAR") { LOG(WARNING) << "Ignoring contour with geometry type: " << type; @@ -449,24 +463,19 @@ } size_t size; - if (!tags.GetSequenceSize(size, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, j, - DICOM_TAG_CONTOUR_IMAGE_SEQUENCE)) || + + imageSequencePath.SetPrefixIndex(1, j); + if (!tags.GetSequenceSize(size, imageSequencePath) || size != 1) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - std::string sopInstanceUid = reader.GetMandatoryStringValue - (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, j, - DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, - DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)); - - std::string slicesData = reader.GetMandatoryStringValue - (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, - DICOM_TAG_CONTOUR_SEQUENCE, j, - DICOM_TAG_CONTOUR_DATA)); + referencedInstancePath.SetPrefixIndex(1, j); + std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath); + + contourDataPath.SetPrefixIndex(1, j); + std::string slicesData = reader.GetMandatoryStringValue(contourDataPath); Vector points; if (!LinearAlgebra::ParseVector(points, slicesData) || @@ -531,6 +540,13 @@ } + Color DicomStructureSet::GetStructureColor(size_t index) const + { + const Structure& s = GetStructure(index); + return Color(s.red_, s.green_, s.blue_); + } + + void DicomStructureSet::GetStructureColor(uint8_t& red, uint8_t& green, uint8_t& blue, @@ -668,50 +684,9 @@ } - DicomStructureSet* DicomStructureSet::SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instanceId) - { - const std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; - OrthancPlugins::FullOrthancDataset dataset(orthanc, uri); - - std::auto_ptr<DicomStructureSet> result(new DicomStructureSet(dataset)); - - std::set<std::string> instances; - result->GetReferencedInstances(instances); - - for (std::set<std::string>::const_iterator it = instances.begin(); - it != instances.end(); ++it) - { - Json::Value lookup; - MessagingToolbox::RestApiPost(lookup, orthanc, "/tools/lookup", *it); - - if (lookup.type() != Json::arrayValue || - lookup.size() != 1 || - !lookup[0].isMember("Type") || - !lookup[0].isMember("Path") || - lookup[0]["Type"].type() != Json::stringValue || - lookup[0]["ID"].type() != Json::stringValue || - lookup[0]["Type"].asString() != "Instance") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); - } - - OrthancPlugins::FullOrthancDataset slice - (orthanc, "/instances/" + lookup[0]["ID"].asString() + "/tags"); - Orthanc::DicomMap m; - MessagingToolbox::ConvertDataset(m, slice); - result->AddReferencedSlice(m); - } - - result->CheckReferencedSlices(); - - return result.release(); - } - - bool DicomStructureSet::ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons, - Structure& structure, - const CoordinateSystem3D& slice) + const Structure& structure, + const CoordinateSystem3D& slice) const { polygons.clear(); @@ -722,7 +697,7 @@ { // This is an axial projection - for (Polygons::iterator polygon = structure.polygons_.begin(); + for (Polygons::const_iterator polygon = structure.polygons_.begin(); polygon != structure.polygons_.end(); ++polygon) { if (polygon->IsOnSlice(slice)) @@ -748,7 +723,7 @@ // Sagittal or coronal projection std::vector<BoostPolygon> projected; - for (Polygons::iterator polygon = structure.polygons_.begin(); + for (Polygons::const_iterator polygon = structure.polygons_.begin(); polygon != structure.polygons_.end(); ++polygon) { double x1, y1, x2, y2;
--- a/Framework/Toolbox/DicomStructureSet.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Toolbox/DicomStructureSet.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,6 +23,7 @@ #include "CoordinateSystem3D.h" #include "Extent2D.h" +#include "../Scene2D/Color.h" #include <Plugins/Samples/Common/FullOrthancDataset.h> @@ -135,13 +136,13 @@ Structure& GetStructure(size_t index); bool ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons, - Structure& structure, - const CoordinateSystem3D& slice); + const Structure& structure, + const CoordinateSystem3D& slice) const; public: DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance); - size_t GetStructureCount() const + size_t GetStructuresCount() const { return structures_.size(); } @@ -152,6 +153,9 @@ const std::string& GetStructureInterpretation(size_t index) const; + Color GetStructureColor(size_t index) const; + + // TODO - remove void GetStructureColor(uint8_t& red, uint8_t& green, uint8_t& blue, @@ -170,12 +174,9 @@ Vector GetNormal() const; - static DicomStructureSet* SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instanceId); - bool ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons, size_t index, - const CoordinateSystem3D& slice) + const CoordinateSystem3D& slice) const { return ProjectStructure(polygons, GetStructure(index), slice); }
--- a/Framework/Toolbox/DownloadStack.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,196 +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 "DownloadStack.h" - -#include <Core/OrthancException.h> - -#include <cassert> - -namespace Deprecated -{ - bool DownloadStack::CheckInvariants() const - { - std::vector<bool> dequeued(nodes_.size(), true); - - int i = firstNode_; - while (i != NIL) - { - const Node& node = nodes_[i]; - - dequeued[i] = false; - - if (node.next_ != NIL && - nodes_[node.next_].prev_ != i) - { - return false; - } - - if (node.prev_ != NIL && - nodes_[node.prev_].next_ != i) - { - return false; - } - - i = nodes_[i].next_; - } - - for (size_t i = 0; i < nodes_.size(); i++) - { - if (nodes_[i].dequeued_ != dequeued[i]) - { - return false; - } - } - - return true; - } - - - DownloadStack::DownloadStack(unsigned int size) - { - nodes_.resize(size); - - if (size == 0) - { - firstNode_ = NIL; - } - else - { - for (size_t i = 0; i < size; i++) - { - nodes_[i].prev_ = static_cast<int>(i - 1); - nodes_[i].next_ = static_cast<int>(i + 1); - nodes_[i].dequeued_ = false; - } - - nodes_.front().prev_ = NIL; - nodes_.back().next_ = NIL; - firstNode_ = 0; - } - - assert(CheckInvariants()); - } - - - DownloadStack::~DownloadStack() - { - assert(CheckInvariants()); - } - - - bool DownloadStack::Pop(unsigned int& value) - { - assert(CheckInvariants()); - - if (firstNode_ == NIL) - { - for (size_t i = 0; i < nodes_.size(); i++) - { - assert(nodes_[i].dequeued_); - } - - return false; - } - else - { - assert(firstNode_ >= 0 && firstNode_ < static_cast<int>(nodes_.size())); - value = firstNode_; - - Node& node = nodes_[firstNode_]; - assert(node.prev_ == NIL); - assert(!node.dequeued_); - - node.dequeued_ = true; - firstNode_ = node.next_; - - if (firstNode_ != NIL) - { - nodes_[firstNode_].prev_ = NIL; - } - - return true; - } - } - - - void DownloadStack::SetTopNodeInternal(unsigned int value) - { - assert(CheckInvariants()); - - Node& node = nodes_[value]; - - if (node.dequeued_) - { - // This node has already been processed by the download thread, nothing to do - return; - } - - // Remove the node from the list - if (node.prev_ == NIL) - { - assert(firstNode_ == static_cast<int>(value)); - - // This is already the top node in the list, nothing to do - return; - } - - nodes_[node.prev_].next_ = node.next_; - - if (node.next_ != NIL) - { - nodes_[node.next_].prev_ = node.prev_; - } - - // Add back the node at the top of the list - assert(firstNode_ != NIL); - - Node& old = nodes_[firstNode_]; - assert(old.prev_ == NIL); - assert(!old.dequeued_); - node.prev_ = NIL; - node.next_ = firstNode_; - old.prev_ = value; - - firstNode_ = value; - } - - - void DownloadStack::SetTopNode(unsigned int value) - { - if (value >= nodes_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - SetTopNodeInternal(value); - } - - - void DownloadStack::SetTopNodePermissive(int value) - { - if (value >= 0 && - value < static_cast<int>(nodes_.size())) - { - SetTopNodeInternal(value); - } - } -}
--- a/Framework/Toolbox/DownloadStack.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +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 <vector> -#include <boost/noncopyable.hpp> - -namespace Deprecated -{ - class DownloadStack : public boost::noncopyable - { - private: - static const int NIL = -1; - - // This is a doubly-linked list - struct Node - { - int next_; - int prev_; - bool dequeued_; - }; - - std::vector<Node> nodes_; - int firstNode_; - - bool CheckInvariants() const; - - void SetTopNodeInternal(unsigned int value); - - public: - DownloadStack(unsigned int size); - - ~DownloadStack(); - - bool Pop(unsigned int& value); - - void SetTopNode(unsigned int value); - - void SetTopNodePermissive(int value); - }; -}
--- a/Framework/Toolbox/FiniteProjectiveCamera.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Toolbox/FiniteProjectiveCamera.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -299,6 +299,7 @@ static void ApplyRaytracerInternal(Orthanc::ImageAccessor& target, const FiniteProjectiveCamera& camera, const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, VolumeProjection projection) { if (source.GetFormat() != SourceFormat || @@ -315,8 +316,8 @@ LOG(WARNING) << "Output image size: " << target.GetWidth() << "x" << target.GetHeight(); LOG(WARNING) << "Output pixel format: " << Orthanc::EnumerationToString(target.GetFormat()); - std::auto_ptr<OrthancStone::ParallelSlices> slices(source.GetGeometry(projection)); - const OrthancStone::Vector pixelSpacing = source.GetGeometry().GetVoxelDimensions(projection); + const unsigned int slicesCount = geometry.GetProjectionDepth(projection); + const OrthancStone::Vector pixelSpacing = geometry.GetVoxelDimensions(projection); const unsigned int targetWidth = target.GetWidth(); const unsigned int targetHeight = target.GetHeight(); @@ -327,11 +328,11 @@ typedef SubpixelReader<SourceFormat, ImageInterpolation_Nearest> SourceReader; - for (size_t z = 0; z < slices->GetSliceCount(); z++) + for (unsigned int z = 0; z < slicesCount; z++) { - LOG(INFO) << "Applying raytracer on slice: " << z << "/" << slices->GetSliceCount(); - - const OrthancStone::CoordinateSystem3D& slice = slices->GetSlice(z); + LOG(INFO) << "Applying raytracer on slice: " << z << "/" << slicesCount; + + OrthancStone::CoordinateSystem3D slice = geometry.GetProjectionSlice(projection, z); OrthancStone::ImageBuffer3D::SliceReader sliceReader(source, projection, static_cast<unsigned int>(z)); SourceReader pixelReader(sliceReader.GetAccessor()); @@ -422,6 +423,7 @@ Orthanc::ImageAccessor* FiniteProjectiveCamera::ApplyRaytracer(const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, Orthanc::PixelFormat targetFormat, unsigned int targetWidth, unsigned int targetHeight, @@ -440,14 +442,14 @@ { ApplyRaytracerInternal<Orthanc::PixelFormat_Grayscale16, Orthanc::PixelFormat_Grayscale16, true> - (*target, *this, source, projection); + (*target, *this, source, geometry, projection); } else if (targetFormat == Orthanc::PixelFormat_Grayscale16 && source.GetFormat() == Orthanc::PixelFormat_Grayscale16 && !mip) { ApplyRaytracerInternal<Orthanc::PixelFormat_Grayscale16, Orthanc::PixelFormat_Grayscale16, false> - (*target, *this, source, projection); + (*target, *this, source, geometry, projection); } else {
--- a/Framework/Toolbox/FiniteProjectiveCamera.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Toolbox/FiniteProjectiveCamera.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,6 +23,7 @@ #include "LinearAlgebra.h" #include "../Volumes/ImageBuffer3D.h" +#include "../Volumes/VolumeImageGeometry.h" namespace OrthancStone { @@ -109,6 +110,7 @@ Vector ApplyGeneral(const Vector& v) const; Orthanc::ImageAccessor* ApplyRaytracer(const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, Orthanc::PixelFormat targetFormat, unsigned int targetWidth, unsigned int targetHeight,
--- a/Framework/Toolbox/IDelayedCallExecutor.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * 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 "../../Framework/Messages/IObserver.h" -#include "../../Framework/Messages/ICallable.h" - -#include <Core/IDynamicObject.h> -#include <Core/Logging.h> - -#include <string> -#include <map> - -namespace Deprecated -{ - // The IDelayedCall executes a callback after a delay (equivalent to timeout() function in javascript). - class IDelayedCallExecutor : public boost::noncopyable - { - protected: - OrthancStone::MessageBroker& broker_; - - public: - ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(__FILE__, __LINE__, TimeoutMessage); - - IDelayedCallExecutor(OrthancStone::MessageBroker& broker) : - broker_(broker) - { - } - - - virtual ~IDelayedCallExecutor() - { - } - - - virtual void Schedule(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback, - unsigned int timeoutInMs = 1000) = 0; - }; -}
--- a/Framework/Toolbox/ISeriesLoader.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +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 "ParallelSlices.h" - -#include <Images/ImageAccessor.h> -#include <Plugins/Samples/Common/IDicomDataset.h> - -namespace OrthancStone -{ - class ISeriesLoader : public boost::noncopyable - { - public: - virtual ~ISeriesLoader() - { - } - - virtual ParallelSlices& GetGeometry() = 0; - - virtual Orthanc::PixelFormat GetPixelFormat() = 0; - - virtual unsigned int GetWidth() = 0; - - virtual unsigned int GetHeight() = 0; - - virtual OrthancPlugins::IDicomDataset* DownloadDicom(size_t index) = 0; - - // This downloads the frame from Orthanc. The resulting pixel - // format must be Grayscale8, Grayscale16, SignedGrayscale16 or - // RGB24. Orthanc Stone assumes the conversion of the photometric - // interpretation is done by Orthanc. - virtual Orthanc::ImageAccessor* DownloadFrame(size_t index) = 0; - - virtual Orthanc::ImageAccessor* DownloadJpegFrame(size_t index, - unsigned int quality) = 0; - - virtual bool IsJpegAvailable() = 0; - }; -}
--- a/Framework/Toolbox/IWebService.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +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 "IWebService.h" - -#include <Core/OrthancException.h> - - -namespace Deprecated -{ - const Orthanc::IDynamicObject& - IWebService::HttpRequestSuccessMessage::GetPayload() const - { - if (HasPayload()) - { - return *payload_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - - const Orthanc::IDynamicObject& - IWebService::HttpRequestErrorMessage::GetPayload() const - { - if (HasPayload()) - { - return *payload_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } -}
--- a/Framework/Toolbox/IWebService.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +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 "../../Framework/Messages/IObserver.h" -#include "../../Framework/Messages/ICallable.h" - -#include <Core/IDynamicObject.h> -#include <Core/Logging.h> - -#include <string> -#include <map> - -namespace Deprecated -{ - // The IWebService performs HTTP requests. - // Since applications can run in native or WASM environment and, since - // in a WASM environment, the WebService is asynchronous, the IWebservice - // also implements an asynchronous interface: you must schedule a request - // and you'll be notified when the response/error is ready. - class IWebService : public boost::noncopyable - { - protected: - OrthancStone::MessageBroker& broker_; - - public: - typedef std::map<std::string, std::string> HttpHeaders; - - class HttpRequestSuccessMessage : public OrthancStone::IMessage - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const std::string& uri_; - const void* answer_; - size_t answerSize_; - const HttpHeaders& answerHeaders_; - const Orthanc::IDynamicObject* payload_; - - public: - HttpRequestSuccessMessage(const std::string& uri, - const void* answer, - size_t answerSize, - const HttpHeaders& answerHeaders, - const Orthanc::IDynamicObject* payload) : - uri_(uri), - answer_(answer), - answerSize_(answerSize), - answerHeaders_(answerHeaders), - payload_(payload) - { - } - - const std::string& GetUri() const - { - return uri_; - } - - const void* GetAnswer() const - { - return answer_; - } - - size_t GetAnswerSize() const - { - return answerSize_; - } - - const HttpHeaders& GetAnswerHttpHeaders() const - { - return answerHeaders_; - } - - bool HasPayload() const - { - return payload_ != NULL; - } - - const Orthanc::IDynamicObject& GetPayload() const; - }; - - - class HttpRequestErrorMessage : public OrthancStone::IMessage - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const std::string& uri_; - const Orthanc::IDynamicObject* payload_; - - public: - HttpRequestErrorMessage(const std::string& uri, - const Orthanc::IDynamicObject* payload) : - uri_(uri), - payload_(payload) - { - } - - const std::string& GetUri() const - { - return uri_; - } - - bool HasPayload() const - { - return payload_ != NULL; - } - - const Orthanc::IDynamicObject& GetPayload() const; - }; - - - IWebService(OrthancStone::MessageBroker& broker) : - broker_(broker) - { - } - - - virtual ~IWebService() - { - } - - virtual void EnableCache(bool enable) = 0; - - virtual void GetAsync(const std::string& uri, - const HttpHeaders& headers, - Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - unsigned int timeoutInSeconds = 60) = 0; - - virtual void PostAsync(const std::string& uri, - const HttpHeaders& headers, - const std::string& body, - Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - unsigned int timeoutInSeconds = 60) = 0; - - virtual void DeleteAsync(const std::string& uri, - const HttpHeaders& headers, - Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - unsigned int timeoutInSeconds = 60) = 0; - }; -}
--- a/Framework/Toolbox/LinearAlgebra.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Toolbox/LinearAlgebra.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -63,21 +63,40 @@ const std::string& value) { std::vector<std::string> items; - Orthanc::Toolbox::TokenizeString(items, value, '\\'); + Orthanc::Toolbox::TokenizeString(items, Orthanc::Toolbox::StripSpaces(value), '\\'); target.resize(items.size()); for (size_t i = 0; i < items.size(); i++) { + /** + * We try and avoid the use of "boost::lexical_cast<>" here, + * as it is very slow. As we are parsing many doubles, we + * prefer to use the standard "std::stod" function if + * available: http://www.cplusplus.com/reference/string/stod/ + **/ + +#if __cplusplus >= 201103L // Is C++11 enabled? try { - target[i] = boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(items[i])); + target[i] = std::stod(items[i]); + } + catch (std::exception&) + { + target.clear(); + return false; + } +#else // Fallback implementation using Boost + try + { + target[i] = boost::lexical_cast<double>(items[i]); } catch (boost::bad_lexical_cast&) { target.clear(); return false; } +#endif } return true;
--- a/Framework/Toolbox/MessagingToolbox.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,456 +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 "MessagingToolbox.h" - -#include <Core/Images/Image.h> -#include <Core/Images/ImageProcessing.h> -#include <Core/Images/JpegReader.h> -#include <Core/Images/PngReader.h> -#include <Core/OrthancException.h> -#include <Core/Toolbox.h> -#include <Core/Logging.h> - -#include <boost/lexical_cast.hpp> -#include <json/reader.h> -#include <json/writer.h> - -namespace OrthancStone -{ - namespace MessagingToolbox - { - static bool ParseVersion(std::string& version, - unsigned int& major, - unsigned int& minor, - unsigned int& patch, - const Json::Value& info) - { - if (info.type() != Json::objectValue || - !info.isMember("Version") || - info["Version"].type() != Json::stringValue) - { - return false; - } - - version = info["Version"].asString(); - if (version == "mainline") - { - // Some arbitrary high values Orthanc versions will never reach ;) - major = 999; - minor = 999; - patch = 999; - return true; - } - - std::vector<std::string> tokens; - Orthanc::Toolbox::TokenizeString(tokens, version, '.'); - - if (tokens.size() != 2 && - tokens.size() != 3) - { - return false; - } - - int a, b, c; - try - { - a = boost::lexical_cast<int>(tokens[0]); - b = boost::lexical_cast<int>(tokens[1]); - - if (tokens.size() == 3) - { - c = boost::lexical_cast<int>(tokens[2]); - } - else - { - c = 0; - } - } - catch (boost::bad_lexical_cast&) - { - return false; - } - - if (a < 0 || - b < 0 || - c < 0) - { - return false; - } - else - { - major = static_cast<unsigned int>(a); - minor = static_cast<unsigned int>(b); - patch = static_cast<unsigned int>(c); - return true; - } - } - - - bool ParseJson(Json::Value& target, - const void* content, - size_t size) - { - Json::Reader reader; - return reader.parse(reinterpret_cast<const char*>(content), - reinterpret_cast<const char*>(content) + size, - target); - } - - void JsonToString(std::string& target, - const Json::Value& source) - { - Json::FastWriter writer; - target = writer.write(source); - } - - static void ParseJsonException(Json::Value& target, - const std::string& source) - { - Json::Reader reader; - if (!reader.parse(source, target)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - - void RestApiGet(Json::Value& target, - OrthancPlugins::IOrthancConnection& orthanc, - const std::string& uri) - { - std::string tmp; - orthanc.RestApiGet(tmp, uri); - ParseJsonException(target, tmp); - } - - - void RestApiPost(Json::Value& target, - OrthancPlugins::IOrthancConnection& orthanc, - const std::string& uri, - const std::string& body) - { - std::string tmp; - orthanc.RestApiPost(tmp, uri, body); - ParseJsonException(target, tmp); - } - - - bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc) - { - try - { - Json::Value json; - RestApiGet(json, orthanc, "/plugins/web-viewer"); - return json.type() == Json::objectValue; - } - catch (Orthanc::OrthancException&) - { - return false; - } - } - - - bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc) - { - Json::Value json; - std::string version; - unsigned int major, minor, patch; - - try - { - RestApiGet(json, orthanc, "/system"); - } - catch (Orthanc::OrthancException&) - { - LOG(ERROR) << "Cannot connect to your Orthanc server"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - if (!ParseVersion(version, major, minor, patch, json)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - LOG(WARNING) << "Version of the Orthanc core (must be above 1.3.1): " << version; - - // Stone is only compatible with Orthanc >= 1.3.1 - if (major < 1 || - (major == 1 && minor < 3) || - (major == 1 && minor == 3 && patch < 1)) - { - return false; - } - - try - { - RestApiGet(json, orthanc, "/plugins/web-viewer"); - } - catch (Orthanc::OrthancException&) - { - // The Web viewer is not installed, this is OK - LOG(WARNING) << "The Web viewer plugin is not installed, progressive download is disabled"; - return true; - } - - if (!ParseVersion(version, major, minor, patch, json)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - LOG(WARNING) << "Version of the Web viewer plugin (must be above 2.2): " << version; - - return (major >= 3 || - (major == 2 && minor >= 2)); - } - - - Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instance, - unsigned int frame, - Orthanc::PixelFormat targetFormat) - { - std::string uri = ("instances/" + instance + "/frames/" + - boost::lexical_cast<std::string>(frame)); - - std::string compressed; - - switch (targetFormat) - { - case Orthanc::PixelFormat_RGB24: - orthanc.RestApiGet(compressed, uri + "/preview"); - break; - - case Orthanc::PixelFormat_Grayscale16: - orthanc.RestApiGet(compressed, uri + "/image-uint16"); - break; - - case Orthanc::PixelFormat_SignedGrayscale16: - orthanc.RestApiGet(compressed, uri + "/image-int16"); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - std::auto_ptr<Orthanc::PngReader> result(new Orthanc::PngReader); - result->ReadFromMemory(compressed); - - if (targetFormat == Orthanc::PixelFormat_SignedGrayscale16) - { - if (result->GetFormat() == Orthanc::PixelFormat_Grayscale16) - { - result->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - } - - return result.release(); - } - - - Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instance, - unsigned int frame, - unsigned int quality, - Orthanc::PixelFormat targetFormat) - { - if (quality <= 0 || - quality > 100) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - // This requires the official Web viewer plugin to be installed! - std::string uri = ("web-viewer/instances/jpeg" + - boost::lexical_cast<std::string>(quality) + - "-" + instance + "_" + - boost::lexical_cast<std::string>(frame)); - - Json::Value encoded; - RestApiGet(encoded, orthanc, uri); - - if (encoded.type() != Json::objectValue || - !encoded.isMember("Orthanc") || - encoded["Orthanc"].type() != Json::objectValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - Json::Value& info = encoded["Orthanc"]; - if (!info.isMember("PixelData") || - !info.isMember("Stretched") || - !info.isMember("Compression") || - info["Compression"].type() != Json::stringValue || - info["PixelData"].type() != Json::stringValue || - info["Stretched"].type() != Json::booleanValue || - info["Compression"].asString() != "Jpeg") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - bool isSigned = false; - bool isStretched = info["Stretched"].asBool(); - - if (info.isMember("IsSigned")) - { - if (info["IsSigned"].type() != Json::booleanValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - else - { - isSigned = info["IsSigned"].asBool(); - } - } - - std::string jpeg; - Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); - - std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader); - reader->ReadFromMemory(jpeg); - - if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image - { - if (targetFormat != Orthanc::PixelFormat_RGB24) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - if (isSigned || isStretched) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - else - { - return reader.release(); - } - } - - if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - if (!isStretched) - { - if (targetFormat != reader->GetFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - return reader.release(); - } - - int32_t stretchLow = 0; - int32_t stretchHigh = 0; - - if (!info.isMember("StretchLow") || - !info.isMember("StretchHigh") || - info["StretchLow"].type() != Json::intValue || - info["StretchHigh"].type() != Json::intValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - stretchLow = info["StretchLow"].asInt(); - stretchHigh = info["StretchHigh"].asInt(); - - if (stretchLow < -32768 || - stretchHigh > 65535 || - (stretchLow < 0 && stretchHigh > 32767)) - { - // This range cannot be represented with a uint16_t or an int16_t - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - // Decode a grayscale JPEG 8bpp image coming from the Web viewer - std::auto_ptr<Orthanc::ImageAccessor> image - (new Orthanc::Image(targetFormat, reader->GetWidth(), reader->GetHeight(), false)); - - float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; - float offset = static_cast<float>(stretchLow) / scaling; - - Orthanc::ImageProcessing::Convert(*image, *reader); - Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); - -#if 0 - /*info.removeMember("PixelData"); - std::cout << info.toStyledString();*/ - - int64_t a, b; - Orthanc::ImageProcessing::GetMinMaxValue(a, b, *image); - std::cout << stretchLow << "->" << stretchHigh << " = " << a << "->" << b << std::endl; -#endif - - return image.release(); - } - - - static void AddTag(Orthanc::DicomMap& target, - const OrthancPlugins::IDicomDataset& source, - const Orthanc::DicomTag& tag) - { - OrthancPlugins::DicomTag key(tag.GetGroup(), tag.GetElement()); - - std::string value; - if (source.GetStringValue(value, key)) - { - target.SetValue(tag, value, false); - } - } - - - void ConvertDataset(Orthanc::DicomMap& target, - const OrthancPlugins::IDicomDataset& source) - { - target.Clear(); - - AddTag(target, source, Orthanc::DICOM_TAG_BITS_ALLOCATED); - AddTag(target, source, Orthanc::DICOM_TAG_BITS_STORED); - AddTag(target, source, Orthanc::DICOM_TAG_COLUMNS); - AddTag(target, source, Orthanc::DICOM_TAG_DOSE_GRID_SCALING); - AddTag(target, source, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER); - AddTag(target, source, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR); - AddTag(target, source, Orthanc::DICOM_TAG_HIGH_BIT); - AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT); - AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT); - AddTag(target, source, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES); - AddTag(target, source, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION); - AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION); - AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_SPACING); - AddTag(target, source, Orthanc::DICOM_TAG_PLANAR_CONFIGURATION); - AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); - AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_SLOPE); - AddTag(target, source, Orthanc::DICOM_TAG_ROWS); - AddTag(target, source, Orthanc::DICOM_TAG_SAMPLES_PER_PIXEL); - AddTag(target, source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); - AddTag(target, source, Orthanc::DICOM_TAG_SLICE_THICKNESS); - AddTag(target, source, Orthanc::DICOM_TAG_SOP_CLASS_UID); - AddTag(target, source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID); - AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_CENTER); - AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_WIDTH); - } - } -}
--- a/Framework/Toolbox/MessagingToolbox.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +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 "../StoneEnumerations.h" - -#include <Core/DicomFormat/DicomMap.h> -#include <Core/Images/ImageAccessor.h> -#include <Plugins/Samples/Common/IDicomDataset.h> -#include <Plugins/Samples/Common/IOrthancConnection.h> - -#include <json/value.h> - -namespace OrthancStone -{ - namespace MessagingToolbox - { - bool ParseJson(Json::Value& target, - const void* content, - size_t size); - - void JsonToString(std::string& target, - const Json::Value& source); - - - void RestApiGet(Json::Value& target, - OrthancPlugins::IOrthancConnection& orthanc, - const std::string& uri); - - void RestApiPost(Json::Value& target, - OrthancPlugins::IOrthancConnection& orthanc, - const std::string& uri, - const std::string& body); - - bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc); - - bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc); - - // This downloads the image from Orthanc and keeps its pixel - // format unchanged (will be either Grayscale8, Grayscale16, - // SignedGrayscale16, or RGB24) - Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instance, - unsigned int frame, - Orthanc::PixelFormat targetFormat); - - Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instance, - unsigned int frame, - unsigned int quality, - Orthanc::PixelFormat targetFormat); - - void ConvertDataset(Orthanc::DicomMap& target, - const OrthancPlugins::IDicomDataset& source); - } -}
--- a/Framework/Toolbox/OrientedBoundingBox.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,268 +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 "OrientedBoundingBox.h" - -#include "GeometryToolbox.h" - -#include <Core/OrthancException.h> - -#include <cassert> - -namespace OrthancStone -{ - OrientedBoundingBox::OrientedBoundingBox(const ImageBuffer3D& image) - { - unsigned int n = image.GetDepth(); - if (n < 1) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); - } - - const CoordinateSystem3D& geometry = image.GetGeometry().GetAxialGeometry(); - Vector dim = image.GetGeometry().GetVoxelDimensions(VolumeProjection_Axial); - - u_ = geometry.GetAxisX(); - v_ = geometry.GetAxisY(); - w_ = geometry.GetNormal(); - - hu_ = static_cast<double>(image.GetWidth() * dim[0] / 2.0); - hv_ = static_cast<double>(image.GetHeight() * dim[1] / 2.0); - hw_ = static_cast<double>(image.GetDepth() * dim[2] / 2.0); - - c_ = (geometry.GetOrigin() + - (hu_ - dim[0] / 2.0) * u_ + - (hv_ - dim[1] / 2.0) * v_ + - (hw_ - dim[2] / 2.0) * w_); - } - - - bool OrientedBoundingBox::HasIntersectionWithPlane(std::vector<Vector>& points, - const Vector& normal, - double d) const - { - assert(normal.size() == 3); - - double r = (hu_ * fabs(boost::numeric::ublas::inner_prod(normal, u_)) + - hv_ * fabs(boost::numeric::ublas::inner_prod(normal, v_)) + - hw_ * fabs(boost::numeric::ublas::inner_prod(normal, w_))); - - double s = boost::numeric::ublas::inner_prod(normal, c_) + d; - - if (fabs(s) >= r) - { - // No intersection, or intersection is reduced to a single point - return false; - } - else - { - Vector p; - - // Loop over all the 12 edges (segments) of the oriented - // bounding box, and check whether they intersect the plane - - // X-aligned edges - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ - u_ * hu_ - v_ * hv_ - w_ * hw_, - c_ + u_ * hu_ - v_ * hv_ - w_ * hw_)) - { - points.push_back(p); - } - - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ - u_ * hu_ + v_ * hv_ - w_ * hw_, - c_ + u_ * hu_ + v_ * hv_ - w_ * hw_)) - { - points.push_back(p); - } - - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ - u_ * hu_ - v_ * hv_ + w_ * hw_, - c_ + u_ * hu_ - v_ * hv_ + w_ * hw_)) - { - points.push_back(p); - } - - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ - u_ * hu_ + v_ * hv_ + w_ * hw_, - c_ + u_ * hu_ + v_ * hv_ + w_ * hw_)) - { - points.push_back(p); - } - - // Y-aligned edges - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ - u_ * hu_ - v_ * hv_ - w_ * hw_, - c_ - u_ * hu_ + v_ * hv_ - w_ * hw_)) - { - points.push_back(p); - } - - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ + u_ * hu_ - v_ * hv_ - w_ * hw_, - c_ + u_ * hu_ + v_ * hv_ - w_ * hw_)) - { - points.push_back(p); - } - - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ - u_ * hu_ - v_ * hv_ + w_ * hw_, - c_ - u_ * hu_ + v_ * hv_ + w_ * hw_)) - { - points.push_back(p); - } - - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ + u_ * hu_ - v_ * hv_ + w_ * hw_, - c_ + u_ * hu_ + v_ * hv_ + w_ * hw_)) - { - points.push_back(p); - } - - // Z-aligned edges - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ - u_ * hu_ - v_ * hv_ - w_ * hw_, - c_ - u_ * hu_ - v_ * hv_ + w_ * hw_)) - { - points.push_back(p); - } - - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ + u_ * hu_ - v_ * hv_ - w_ * hw_, - c_ + u_ * hu_ - v_ * hv_ + w_ * hw_)) - { - points.push_back(p); - } - - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ - u_ * hu_ + v_ * hv_ - w_ * hw_, - c_ - u_ * hu_ + v_ * hv_ + w_ * hw_)) - { - points.push_back(p); - } - - if (GeometryToolbox::IntersectPlaneAndSegment - (p, normal, d, - c_ + u_ * hu_ + v_ * hv_ - w_ * hw_, - c_ + u_ * hu_ + v_ * hv_ + w_ * hw_)) - { - points.push_back(p); - } - - return true; - } - } - - - bool OrientedBoundingBox::HasIntersection(std::vector<Vector>& points, - const CoordinateSystem3D& plane) const - { - // From the vector equation of a 3D plane (specified by origin - // and normal), to the general equation of a 3D plane (which - // looses information about the origin of the coordinate system) - const Vector& normal = plane.GetNormal(); - const Vector& origin = plane.GetOrigin(); - double d = -(normal[0] * origin[0] + normal[1] * origin[1] + normal[2] * origin[2]); - - return HasIntersectionWithPlane(points, normal, d); - } - - - bool OrientedBoundingBox::Contains(const Vector& p) const - { - assert(p.size() == 3); - - const Vector q = p - c_; - - return (fabs(boost::numeric::ublas::inner_prod(q, u_)) <= hu_ && - fabs(boost::numeric::ublas::inner_prod(q, v_)) <= hv_ && - fabs(boost::numeric::ublas::inner_prod(q, w_)) <= hw_); - } - - - void OrientedBoundingBox::FromInternalCoordinates(Vector& target, - double x, - double y, - double z) const - { - target = (c_ + - u_ * 2.0 * hu_ * (x - 0.5) + - v_ * 2.0 * hv_ * (y - 0.5) + - w_ * 2.0 * hw_ * (z - 0.5)); - } - - - void OrientedBoundingBox::FromInternalCoordinates(Vector& target, - const Vector& source) const - { - assert(source.size() == 3); - FromInternalCoordinates(target, source[0], source[1], source[2]); - } - - - void OrientedBoundingBox::ToInternalCoordinates(Vector& target, - const Vector& source) const - { - assert(source.size() == 3); - const Vector q = source - c_; - - double x = boost::numeric::ublas::inner_prod(q, u_) / (2.0 * hu_) + 0.5; - double y = boost::numeric::ublas::inner_prod(q, v_) / (2.0 * hv_) + 0.5; - double z = boost::numeric::ublas::inner_prod(q, w_) / (2.0 * hw_) + 0.5; - - LinearAlgebra::AssignVector(target, x, y, z); - } - - - bool OrientedBoundingBox::ComputeExtent(Extent2D& extent, - const CoordinateSystem3D& plane) const - { - extent.Reset(); - - std::vector<Vector> points; - if (HasIntersection(points, plane)) - { - for (size_t i = 0; i < points.size(); i++) - { - double x, y; - plane.ProjectPoint(x, y, points[i]); - extent.AddPoint(x, y); - } - - return true; - } - else - { - return false; - } - } -}
--- a/Framework/Toolbox/OrientedBoundingBox.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +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 "Extent2D.h" -#include "LinearAlgebra.h" -#include "../Volumes/ImageBuffer3D.h" - -namespace OrthancStone -{ - class OrientedBoundingBox : public boost::noncopyable - { - private: - Vector c_; // center - Vector u_; // normalized width vector - Vector v_; // normalized height vector - Vector w_; // normalized depth vector - double hu_; // half width - double hv_; // half height - double hw_; // half depth - - public: - OrientedBoundingBox(const ImageBuffer3D& image); - - const Vector& GetCenter() const - { - return c_; - } - - bool HasIntersectionWithPlane(std::vector<Vector>& points, - const Vector& normal, - double d) const; - - bool HasIntersection(std::vector<Vector>& points, - const CoordinateSystem3D& plane) const; - - bool Contains(const Vector& p) const; - - void FromInternalCoordinates(Vector& target, - double x, - double y, - double z) const; - - void FromInternalCoordinates(Vector& target, - const Vector& source) const; - - void ToInternalCoordinates(Vector& target, - const Vector& source) const; - - bool ComputeExtent(Extent2D& extent, - const CoordinateSystem3D& plane) const; - }; -} -
--- a/Framework/Toolbox/OrthancApiClient.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,337 +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 "OrthancApiClient.h" - -#include "MessagingToolbox.h" -#include "Framework/Toolbox/MessagingToolbox.h" - -#include <Core/OrthancException.h> - -namespace Deprecated -{ - const Orthanc::IDynamicObject& OrthancApiClient::JsonResponseReadyMessage::GetPayload() const - { - if (HasPayload()) - { - return *payload_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - - const Orthanc::IDynamicObject& OrthancApiClient::BinaryResponseReadyMessage::GetPayload() const - { - if (HasPayload()) - { - return *payload_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - - const Orthanc::IDynamicObject& OrthancApiClient::EmptyResponseReadyMessage::GetPayload() const - { - if (HasPayload()) - { - return *payload_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - - class OrthancApiClient::WebServicePayload : public Orthanc::IDynamicObject - { - private: - std::auto_ptr< OrthancStone::MessageHandler<EmptyResponseReadyMessage> > emptyHandler_; - std::auto_ptr< OrthancStone::MessageHandler<JsonResponseReadyMessage> > jsonHandler_; - std::auto_ptr< OrthancStone::MessageHandler<BinaryResponseReadyMessage> > binaryHandler_; - std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> > failureHandler_; - std::auto_ptr< Orthanc::IDynamicObject > userPayload_; - - void NotifyConversionError(const IWebService::HttpRequestSuccessMessage& message) const - { - if (failureHandler_.get() != NULL) - { - failureHandler_->Apply(IWebService::HttpRequestErrorMessage - (message.GetUri(), userPayload_.get())); - } - } - - public: - WebServicePayload(OrthancStone::MessageHandler<EmptyResponseReadyMessage>* handler, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, - Orthanc::IDynamicObject* userPayload) : - emptyHandler_(handler), - failureHandler_(failureHandler), - userPayload_(userPayload) - { - if (handler == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - WebServicePayload(OrthancStone::MessageHandler<BinaryResponseReadyMessage>* handler, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, - Orthanc::IDynamicObject* userPayload) : - binaryHandler_(handler), - failureHandler_(failureHandler), - userPayload_(userPayload) - { - if (handler == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - WebServicePayload(OrthancStone::MessageHandler<JsonResponseReadyMessage>* handler, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, - Orthanc::IDynamicObject* userPayload) : - jsonHandler_(handler), - failureHandler_(failureHandler), - userPayload_(userPayload) - { - if (handler == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - void HandleSuccess(const IWebService::HttpRequestSuccessMessage& message) const - { - if (emptyHandler_.get() != NULL) - { - emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage - (message.GetUri(), userPayload_.get())); - } - else if (binaryHandler_.get() != NULL) - { - binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage - (message.GetUri(), message.GetAnswer(), - message.GetAnswerSize(), userPayload_.get())); - } - else if (jsonHandler_.get() != NULL) - { - Json::Value response; - if (OrthancStone::MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) - { - jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage - (message.GetUri(), response, userPayload_.get())); - } - else - { - NotifyConversionError(message); - } - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - void HandleFailure(const IWebService::HttpRequestErrorMessage& message) const - { - if (failureHandler_.get() != NULL) - { - failureHandler_->Apply(IWebService::HttpRequestErrorMessage - (message.GetUri(), userPayload_.get())); - } - } - }; - - - OrthancApiClient::OrthancApiClient(OrthancStone::MessageBroker& broker, - IWebService& web, - const std::string& baseUrl) : - IObservable(broker), - IObserver(broker), - web_(web), - baseUrl_(baseUrl) - { - } - - - void OrthancApiClient::GetJsonAsync( - const std::string& uri, - OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, - Orthanc::IDynamicObject* payload) - { - IWebService::HttpHeaders emptyHeaders; - web_.GetAsync(baseUrl_ + uri, - emptyHeaders, - new WebServicePayload(successCallback, failureCallback, payload), - new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> - (*this, &OrthancApiClient::NotifyHttpSuccess), - new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> - (*this, &OrthancApiClient::NotifyHttpError)); - } - - - void OrthancApiClient::GetBinaryAsync( - const std::string& uri, - const std::string& contentType, - OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, - Orthanc::IDynamicObject* payload) - { - IWebService::HttpHeaders headers; - headers["Accept"] = contentType; - GetBinaryAsync(uri, headers, successCallback, failureCallback, payload); - } - - void OrthancApiClient::GetBinaryAsync( - const std::string& uri, - const IWebService::HttpHeaders& headers, - OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, - Orthanc::IDynamicObject* payload) - { - // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str()); - - web_.GetAsync(baseUrl_ + uri, headers, - new WebServicePayload(successCallback, failureCallback, payload), - new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> - (*this, &OrthancApiClient::NotifyHttpSuccess), - new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> - (*this, &OrthancApiClient::NotifyHttpError)); - } - - - void OrthancApiClient::PostBinaryAsyncExpectJson( - const std::string& uri, - const std::string& body, - OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, - Orthanc::IDynamicObject* payload) - { - web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(successCallback, failureCallback, payload), - new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> - (*this, &OrthancApiClient::NotifyHttpSuccess), - new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> - (*this, &OrthancApiClient::NotifyHttpError)); - - } - - void OrthancApiClient::PostBinaryAsync( - const std::string& uri, - const std::string& body) - { - web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, NULL, NULL, NULL); - } - - void OrthancApiClient::PostBinaryAsync( - const std::string& uri, - const std::string& body, - OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, - Orthanc::IDynamicObject* payload /* takes ownership */) - { - web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(successCallback, failureCallback, payload), - new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> - (*this, &OrthancApiClient::NotifyHttpSuccess), - new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> - (*this, &OrthancApiClient::NotifyHttpError)); - } - - void OrthancApiClient::PostJsonAsyncExpectJson( - const std::string& uri, - const Json::Value& data, - OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, - Orthanc::IDynamicObject* payload) - { - std::string body; - OrthancStone::MessagingToolbox::JsonToString(body, data); - return PostBinaryAsyncExpectJson(uri, body, successCallback, failureCallback, payload); - } - - void OrthancApiClient::PostJsonAsync( - const std::string& uri, - const Json::Value& data) - { - std::string body; - OrthancStone::MessagingToolbox::JsonToString(body, data); - return PostBinaryAsync(uri, body); - } - - void OrthancApiClient::PostJsonAsync( - const std::string& uri, - const Json::Value& data, - OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, - Orthanc::IDynamicObject* payload /* takes ownership */) - { - std::string body; - OrthancStone::MessagingToolbox::JsonToString(body, data); - return PostBinaryAsync(uri, body, successCallback, failureCallback, payload); - } - - void OrthancApiClient::DeleteAsync( - const std::string& uri, - OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, - Orthanc::IDynamicObject* payload) - { - web_.DeleteAsync(baseUrl_ + uri, IWebService::HttpHeaders(), - new WebServicePayload(successCallback, failureCallback, payload), - new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> - (*this, &OrthancApiClient::NotifyHttpSuccess), - new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> - (*this, &OrthancApiClient::NotifyHttpError)); - } - - - void OrthancApiClient::NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message) - { - if (message.HasPayload()) - { - dynamic_cast<const WebServicePayload&>(message.GetPayload()).HandleSuccess(message); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - void OrthancApiClient::NotifyHttpError(const IWebService::HttpRequestErrorMessage& message) - { - if (message.HasPayload()) - { - dynamic_cast<const WebServicePayload&>(message.GetPayload()).HandleFailure(message); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } -}
--- a/Framework/Toolbox/OrthancApiClient.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,243 +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/shared_ptr.hpp> -#include <json/json.h> - -#include "IWebService.h" -#include "../Messages/IObservable.h" -#include "../Messages/Promise.h" - -namespace Deprecated -{ - class OrthancApiClient : - public OrthancStone::IObservable, - public OrthancStone::IObserver - { - public: - class JsonResponseReadyMessage : public OrthancStone::IMessage - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const std::string& uri_; - const Json::Value& json_; - const Orthanc::IDynamicObject* payload_; - - public: - JsonResponseReadyMessage(const std::string& uri, - const Json::Value& json, - const Orthanc::IDynamicObject* payload) : - uri_(uri), - json_(json), - payload_(payload) - { - } - - const std::string& GetUri() const - { - return uri_; - } - - const Json::Value& GetJson() const - { - return json_; - } - - bool HasPayload() const - { - return payload_ != NULL; - } - - const Orthanc::IDynamicObject& GetPayload() const; - }; - - - class BinaryResponseReadyMessage : public OrthancStone::IMessage - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const std::string& uri_; - const void* answer_; - size_t answerSize_; - const Orthanc::IDynamicObject* payload_; - - public: - BinaryResponseReadyMessage(const std::string& uri, - const void* answer, - size_t answerSize, - const Orthanc::IDynamicObject* payload) : - uri_(uri), - answer_(answer), - answerSize_(answerSize), - payload_(payload) - { - } - - const std::string& GetUri() const - { - return uri_; - } - - const void* GetAnswer() const - { - return answer_; - } - - size_t GetAnswerSize() const - { - return answerSize_; - } - - bool HasPayload() const - { - return payload_ != NULL; - } - - const Orthanc::IDynamicObject& GetPayload() const; - }; - - - class EmptyResponseReadyMessage : public OrthancStone::IMessage - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const std::string& uri_; - const Orthanc::IDynamicObject* payload_; - - public: - EmptyResponseReadyMessage(const std::string& uri, - const Orthanc::IDynamicObject* payload) : - uri_(uri), - payload_(payload) - { - } - - const std::string& GetUri() const - { - return uri_; - } - - bool HasPayload() const - { - return payload_ != NULL; - } - - const Orthanc::IDynamicObject& GetPayload() const; - }; - - - - private: - class WebServicePayload; - - protected: - IWebService& web_; - std::string baseUrl_; - - public: - OrthancApiClient(OrthancStone::MessageBroker& broker, - IWebService& web, - const std::string& baseUrl); - - virtual ~OrthancApiClient() - { - } - - const std::string& GetBaseUrl() const {return baseUrl_;} - - // schedule a GET request expecting a JSON response. - void GetJsonAsync(const std::string& uri, - OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - Orthanc::IDynamicObject* payload = NULL /* takes ownership */); - - // schedule a GET request expecting a binary response. - void GetBinaryAsync(const std::string& uri, - const std::string& contentType, - OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - Orthanc::IDynamicObject* payload = NULL /* takes ownership */); - - // schedule a GET request expecting a binary response. - void GetBinaryAsync(const std::string& uri, - const IWebService::HttpHeaders& headers, - OrthancStone::MessageHandler<BinaryResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - Orthanc::IDynamicObject* payload = NULL /* takes ownership */); - - // schedule a POST request expecting a JSON response. - void PostBinaryAsyncExpectJson(const std::string& uri, - const std::string& body, - OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - Orthanc::IDynamicObject* payload = NULL /* takes ownership */); - - // schedule a POST request expecting a JSON response. - void PostJsonAsyncExpectJson(const std::string& uri, - const Json::Value& data, - OrthancStone::MessageHandler<JsonResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - Orthanc::IDynamicObject* payload = NULL /* takes ownership */); - - // schedule a POST request and don't mind the response. - 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, - OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, - OrthancStone::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, - OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, - OrthancStone::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, - OrthancStone::MessageHandler<EmptyResponseReadyMessage>* successCallback, - OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, - Orthanc::IDynamicObject* payload = NULL /* takes ownership */); - - void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message); - - void NotifyHttpError(const IWebService::HttpRequestErrorMessage& message); - - private: - void HandleFromCache(const std::string& uri, - const IWebService::HttpHeaders& headers, - Orthanc::IDynamicObject* payload /* takes ownership */); - }; -}
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,904 +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 "OrthancSlicesLoader.h" - -#include "MessagingToolbox.h" - -#include <Core/Compression/GzipCompressor.h> -#include <Core/Endianness.h> -#include <Core/Images/Image.h> -#include <Core/Images/ImageProcessing.h> -#include <Core/Images/JpegReader.h> -#include <Core/Images/PngReader.h> -#include <Core/Images/PamReader.h> -#include <Core/Logging.h> -#include <Core/OrthancException.h> -#include <Core/Toolbox.h> -#include <Plugins/Samples/Common/FullOrthancDataset.h> - -#include <boost/lexical_cast.hpp> - - - -/** - * TODO This is a SLOW implementation of base64 decoding, because - * "Orthanc::Toolbox::DecodeBase64()" does not work properly with - * WASM. UNDERSTAND WHY. - * https://stackoverflow.com/a/34571089/881731 - **/ -static std::string base64_decode(const std::string &in) -{ - std::string out; - - std::vector<int> T(256,-1); - for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; - - int val=0, valb=-8; - for (size_t i = 0; i < in.size(); i++) { - unsigned char c = in[i]; - if (T[c] == -1) break; - val = (val<<6) + T[c]; - valb += 6; - if (valb>=0) { - out.push_back(char((val>>valb)&0xFF)); - valb-=8; - } - } - return out; -} - - - -namespace Deprecated -{ - class OrthancSlicesLoader::Operation : public Orthanc::IDynamicObject - { - private: - Mode mode_; - unsigned int frame_; - unsigned int sliceIndex_; - const Slice* slice_; - std::string instanceId_; - OrthancStone::SliceImageQuality quality_; - - Operation(Mode mode) : - mode_(mode) - { - } - - public: - Mode GetMode() const - { - return mode_; - } - - OrthancStone::SliceImageQuality GetQuality() const - { - assert(mode_ == Mode_LoadImage || - mode_ == Mode_LoadRawImage); - return quality_; - } - - unsigned int GetSliceIndex() const - { - assert(mode_ == Mode_LoadImage || - mode_ == Mode_LoadRawImage); - return sliceIndex_; - } - - const Slice& GetSlice() const - { - assert(mode_ == Mode_LoadImage || - mode_ == Mode_LoadRawImage); - assert(slice_ != NULL); - return *slice_; - } - - unsigned int GetFrame() const - { - assert(mode_ == Mode_FrameGeometry); - return frame_; - } - - const std::string& GetInstanceId() const - { - assert(mode_ == Mode_FrameGeometry || - mode_ == Mode_InstanceGeometry); - return instanceId_; - } - - static Operation* DownloadInstanceGeometry(const std::string& instanceId) - { - std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry)); - operation->instanceId_ = instanceId; - return operation.release(); - } - - static Operation* DownloadFrameGeometry(const std::string& instanceId, - unsigned int frame) - { - std::auto_ptr<Operation> operation(new Operation(Mode_FrameGeometry)); - operation->instanceId_ = instanceId; - operation->frame_ = frame; - return operation.release(); - } - - static Operation* DownloadSliceImage(unsigned int sliceIndex, - const Slice& slice, - OrthancStone::SliceImageQuality quality) - { - std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage)); - tmp->sliceIndex_ = sliceIndex; - tmp->slice_ = &slice; - tmp->quality_ = quality; - return tmp.release(); - } - - static Operation* DownloadSliceRawImage(unsigned int sliceIndex, - const Slice& slice) - { - std::auto_ptr<Operation> tmp(new Operation(Mode_LoadRawImage)); - tmp->sliceIndex_ = sliceIndex; - tmp->slice_ = &slice; - tmp->quality_ = OrthancStone::SliceImageQuality_InternalRaw; - return tmp.release(); - } - - static Operation* DownloadDicomFile(const Slice& slice) - { - std::auto_ptr<Operation> tmp(new Operation(Mode_LoadDicomFile)); - tmp->slice_ = &slice; - return tmp.release(); - } - - }; - - void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation, - const Orthanc::ImageAccessor& image) - { - OrthancSlicesLoader::SliceImageReadyMessage msg - (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality()); - BroadcastMessage(msg); - } - - - void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) - { - OrthancSlicesLoader::SliceImageErrorMessage msg - (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality()); - BroadcastMessage(msg); - } - - - void OrthancSlicesLoader::SortAndFinalizeSlices() - { - bool ok = slices_.Sort(); - - state_ = State_GeometryReady; - - if (ok) - { - LOG(INFO) << "Loaded a series with " << slices_.GetSlicesCount() << " slice(s)"; - BroadcastMessage(SliceGeometryReadyMessage(*this)); - } - else - { - LOG(ERROR) << "This series is empty"; - BroadcastMessage(SliceGeometryErrorMessage(*this)); - } - } - - void OrthancSlicesLoader::OnGeometryError(const IWebService::HttpRequestErrorMessage& message) - { - BroadcastMessage(SliceGeometryErrorMessage(*this)); - state_ = State_Error; - } - - void OrthancSlicesLoader::OnSliceImageError(const IWebService::HttpRequestErrorMessage& message) - { - NotifySliceImageError(dynamic_cast<const Operation&>(message.GetPayload())); - state_ = State_Error; - } - - void OrthancSlicesLoader::ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message) - { - const Json::Value& series = message.GetJson(); - Json::Value::Members instances = series.getMemberNames(); - - slices_.Reserve(instances.size()); - - for (size_t i = 0; i < instances.size(); i++) - { - OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]); - - Orthanc::DicomMap dicom; - OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset); - - unsigned int frames; - if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) - { - frames = 1; - } - - for (unsigned int frame = 0; frame < frames; frame++) - { - std::auto_ptr<Slice> slice(new Slice); - if (slice->ParseOrthancFrame(dicom, instances[i], frame)) - { - OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry(); - slices_.AddSlice(geometry, slice.release()); - } - else - { - LOG(WARNING) << "Skipping invalid frame " << frame << " within instance " << instances[i]; - } - } - } - - SortAndFinalizeSlices(); - } - - void OrthancSlicesLoader::ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message) - { - const Json::Value& tags = message.GetJson(); - const std::string& instanceId = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetInstanceId(); - - OrthancPlugins::FullOrthancDataset dataset(tags); - - Orthanc::DicomMap dicom; - OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset); - - unsigned int frames; - if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) - { - frames = 1; - } - - LOG(INFO) << "Instance " << instanceId << " contains " << frames << " frame(s)"; - - for (unsigned int frame = 0; frame < frames; frame++) - { - std::auto_ptr<Slice> slice(new Slice); - if (slice->ParseOrthancFrame(dicom, instanceId, frame)) - { - OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry(); - slices_.AddSlice(geometry, slice.release()); - } - else - { - LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId; - BroadcastMessage(SliceGeometryErrorMessage(*this)); - return; - } - } - - SortAndFinalizeSlices(); - } - - - void OrthancSlicesLoader::ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message) - { - const Json::Value& tags = message.GetJson(); - const std::string& instanceId = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetInstanceId(); - unsigned int frame = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetFrame(); - - OrthancPlugins::FullOrthancDataset dataset(tags); - - state_ = State_GeometryReady; - - Orthanc::DicomMap dicom; - OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset); - - std::auto_ptr<Slice> slice(new Slice); - if (slice->ParseOrthancFrame(dicom, instanceId, frame)) - { - LOG(INFO) << "Loaded instance geometry " << instanceId; - - OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry(); - slices_.AddSlice(geometry, slice.release()); - - BroadcastMessage(SliceGeometryReadyMessage(*this)); - } - else - { - LOG(WARNING) << "Skipping invalid instance " << instanceId; - BroadcastMessage(SliceGeometryErrorMessage(*this)); - } - } - - - void OrthancSlicesLoader::ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message) - { - const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()); - std::auto_ptr<Orthanc::ImageAccessor> image; - - try - { - image.reset(new Orthanc::PngReader); - dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(message.GetAnswer(), message.GetAnswerSize()); - } - catch (Orthanc::OrthancException&) - { - NotifySliceImageError(operation); - return; - } - - if (image->GetWidth() != operation.GetSlice().GetWidth() || - image->GetHeight() != operation.GetSlice().GetHeight()) - { - NotifySliceImageError(operation); - return; - } - - if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() == - Orthanc::PixelFormat_SignedGrayscale16) - { - if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16) - { - image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); - } - else - { - NotifySliceImageError(operation); - return; - } - } - - NotifySliceImageSuccess(operation, *image); - } - - void OrthancSlicesLoader::ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message) - { - const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()); - std::auto_ptr<Orthanc::ImageAccessor> image; - - try - { - image.reset(new Orthanc::PamReader); - dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(message.GetAnswer(), message.GetAnswerSize()); - } - catch (Orthanc::OrthancException&) - { - NotifySliceImageError(operation); - return; - } - - if (image->GetWidth() != operation.GetSlice().GetWidth() || - image->GetHeight() != operation.GetSlice().GetHeight()) - { - NotifySliceImageError(operation); - return; - } - - if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() == - Orthanc::PixelFormat_SignedGrayscale16) - { - if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16) - { - image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); - } - else - { - NotifySliceImageError(operation); - return; - } - } - - NotifySliceImageSuccess(operation, *image); - } - - - void OrthancSlicesLoader::ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message) - { - const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()); - - const Json::Value& encoded = message.GetJson(); - if (encoded.type() != Json::objectValue || - !encoded.isMember("Orthanc") || - encoded["Orthanc"].type() != Json::objectValue) - { - NotifySliceImageError(operation); - return; - } - - const Json::Value& info = encoded["Orthanc"]; - if (!info.isMember("PixelData") || - !info.isMember("Stretched") || - !info.isMember("Compression") || - info["Compression"].type() != Json::stringValue || - info["PixelData"].type() != Json::stringValue || - info["Stretched"].type() != Json::booleanValue || - info["Compression"].asString() != "Jpeg") - { - NotifySliceImageError(operation); - return; - } - - bool isSigned = false; - bool isStretched = info["Stretched"].asBool(); - - if (info.isMember("IsSigned")) - { - if (info["IsSigned"].type() != Json::booleanValue) - { - NotifySliceImageError(operation); - return; - } - else - { - isSigned = info["IsSigned"].asBool(); - } - } - - std::auto_ptr<Orthanc::ImageAccessor> reader; - - { - std::string jpeg; - //Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); - jpeg = base64_decode(info["PixelData"].asString()); - - try - { - reader.reset(new Orthanc::JpegReader); - dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg); - } - catch (Orthanc::OrthancException&) - { - NotifySliceImageError(operation); - return; - } - } - - Orthanc::PixelFormat expectedFormat = - operation.GetSlice().GetConverter().GetExpectedPixelFormat(); - - if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image - { - if (expectedFormat != Orthanc::PixelFormat_RGB24) - { - NotifySliceImageError(operation); - return; - } - - if (isSigned || isStretched) - { - NotifySliceImageError(operation); - return; - } - else - { - NotifySliceImageSuccess(operation, *reader); - return; - } - } - - if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) - { - NotifySliceImageError(operation); - return; - } - - if (!isStretched) - { - if (expectedFormat != reader->GetFormat()) - { - NotifySliceImageError(operation); - return; - } - else - { - NotifySliceImageSuccess(operation, *reader); - return; - } - } - - int32_t stretchLow = 0; - int32_t stretchHigh = 0; - - if (!info.isMember("StretchLow") || - !info.isMember("StretchHigh") || - info["StretchLow"].type() != Json::intValue || - info["StretchHigh"].type() != Json::intValue) - { - NotifySliceImageError(operation); - return; - } - - stretchLow = info["StretchLow"].asInt(); - stretchHigh = info["StretchHigh"].asInt(); - - if (stretchLow < -32768 || - stretchHigh > 65535 || - (stretchLow < 0 && stretchHigh > 32767)) - { - // This range cannot be represented with a uint16_t or an int16_t - NotifySliceImageError(operation); - return; - } - - // Decode a grayscale JPEG 8bpp image coming from the Web viewer - std::auto_ptr<Orthanc::ImageAccessor> image - (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false)); - - Orthanc::ImageProcessing::Convert(*image, *reader); - reader.reset(); - - float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; - - if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling)) - { - float offset = static_cast<float>(stretchLow) / scaling; - Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); - } - - NotifySliceImageSuccess(operation, *image); - } - - - class StringImage : public Orthanc::ImageAccessor - { - private: - std::string buffer_; - - public: - StringImage(Orthanc::PixelFormat format, - unsigned int width, - unsigned int height, - std::string& buffer) - { - if (buffer.size() != Orthanc::GetBytesPerPixel(format) * width * height) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - buffer_.swap(buffer); // The source buffer is now empty - - void* data = (buffer_.empty() ? NULL : &buffer_[0]); - - AssignWritable(format, width, height, - Orthanc::GetBytesPerPixel(format) * width, data); - } - }; - - void OrthancSlicesLoader::ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message) - { - const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()); - Orthanc::GzipCompressor compressor; - - std::string raw; - compressor.Uncompress(raw, message.GetAnswer(), message.GetAnswerSize()); - - const Orthanc::DicomImageInformation& info = operation.GetSlice().GetImageInformation(); - - if (info.GetBitsAllocated() == 32 && - info.GetBitsStored() == 32 && - info.GetHighBit() == 31 && - info.GetChannelCount() == 1 && - !info.IsSigned() && - info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 && - raw.size() == info.GetWidth() * info.GetHeight() * 4) - { - // This is the case of RT-DOSE (uint32_t values) - - std::auto_ptr<Orthanc::ImageAccessor> image - (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(), - info.GetHeight(), raw)); - - // TODO - Only for big endian - for (unsigned int y = 0; y < image->GetHeight(); y++) - { - uint32_t *p = reinterpret_cast<uint32_t*>(image->GetRow(y)); - for (unsigned int x = 0; x < image->GetWidth(); x++, p++) - { - *p = le32toh(*p); - } - } - - NotifySliceImageSuccess(operation, *image); - } - else if (info.GetBitsAllocated() == 16 && - info.GetBitsStored() == 16 && - info.GetHighBit() == 15 && - info.GetChannelCount() == 1 && - !info.IsSigned() && - info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 && - raw.size() == info.GetWidth() * info.GetHeight() * 2) - { - std::auto_ptr<Orthanc::ImageAccessor> image - (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(), - info.GetHeight(), raw)); - - // TODO - Big endian ? - - NotifySliceImageSuccess(operation, *image); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - } - - - OrthancSlicesLoader::OrthancSlicesLoader(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc) : - OrthancStone::IObservable(broker), - OrthancStone::IObserver(broker), - orthanc_(orthanc), - state_(State_Initialization) - { - } - - - void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId) - { - if (state_ != State_Initialization) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - state_ = State_LoadingGeometry; - orthanc_.GetJsonAsync("/series/" + seriesId + "/instances-tags", - new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSeriesGeometry), - new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError), - NULL); - } - } - - void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId) - { - if (state_ != State_Initialization) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - state_ = State_LoadingGeometry; - - // Tag "3004-000c" is "Grid Frame Offset Vector", which is - // mandatory to read RT DOSE, but is too long to be returned by default - orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c", - new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseInstanceGeometry), - new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError), - Operation::DownloadInstanceGeometry(instanceId)); - } - } - - - void OrthancSlicesLoader::ScheduleLoadFrame(const std::string& instanceId, - unsigned int frame) - { - if (state_ != State_Initialization) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - state_ = State_LoadingGeometry; - - orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags", - new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseFrameGeometry), - new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError), - Operation::DownloadFrameGeometry(instanceId, frame)); - } - } - - - bool OrthancSlicesLoader::IsGeometryReady() const - { - return state_ == State_GeometryReady; - } - - - size_t OrthancSlicesLoader::GetSlicesCount() const - { - if (state_ != State_GeometryReady) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return slices_.GetSlicesCount(); - } - - - const Slice& OrthancSlicesLoader::GetSlice(size_t index) const - { - if (state_ != State_GeometryReady) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return dynamic_cast<const Slice&>(slices_.GetSlicePayload(index)); - } - - - bool OrthancSlicesLoader::LookupSlice(size_t& index, - const OrthancStone::CoordinateSystem3D& plane) const - { - if (state_ != State_GeometryReady) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - double distance; - return (slices_.LookupClosestSlice(index, distance, plane) && - distance <= GetSlice(index).GetThickness() / 2.0); - } - - - void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice, - size_t index) - { - std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + - boost::lexical_cast<std::string>(slice.GetFrame())); - - switch (slice.GetConverter().GetExpectedPixelFormat()) - { - case Orthanc::PixelFormat_RGB24: - uri += "/preview"; - break; - - case Orthanc::PixelFormat_Grayscale16: - uri += "/image-uint16"; - break; - - case Orthanc::PixelFormat_SignedGrayscale16: - uri += "/image-int16"; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - orthanc_.GetBinaryAsync(uri, "image/png", - new OrthancStone::Callable<OrthancSlicesLoader, - OrthancApiClient::BinaryResponseReadyMessage> - (*this, &OrthancSlicesLoader::ParseSliceImagePng), - new OrthancStone::Callable<OrthancSlicesLoader, - IWebService::HttpRequestErrorMessage> - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceImage( - static_cast<unsigned int>(index), slice, OrthancStone::SliceImageQuality_FullPng)); -} - - void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice, - size_t index) - { - std::string uri = - ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + - boost::lexical_cast<std::string>(slice.GetFrame())); - - switch (slice.GetConverter().GetExpectedPixelFormat()) - { - case Orthanc::PixelFormat_RGB24: - uri += "/preview"; - break; - - case Orthanc::PixelFormat_Grayscale16: - uri += "/image-uint16"; - break; - - case Orthanc::PixelFormat_SignedGrayscale16: - uri += "/image-int16"; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - orthanc_.GetBinaryAsync(uri, "image/x-portable-arbitrarymap", - new OrthancStone::Callable<OrthancSlicesLoader, - OrthancApiClient::BinaryResponseReadyMessage> - (*this, &OrthancSlicesLoader::ParseSliceImagePam), - new OrthancStone::Callable<OrthancSlicesLoader, - IWebService::HttpRequestErrorMessage> - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceImage(static_cast<unsigned int>(index), - slice, OrthancStone::SliceImageQuality_FullPam)); - } - - - - void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice, - size_t index, - OrthancStone::SliceImageQuality quality) - { - unsigned int value; - - switch (quality) - { - case OrthancStone::SliceImageQuality_Jpeg50: - value = 50; - break; - - case OrthancStone::SliceImageQuality_Jpeg90: - value = 90; - break; - - case OrthancStone::SliceImageQuality_Jpeg95: - value = 95; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - // This requires the official Web viewer plugin to be installed! - std::string uri = ("/web-viewer/instances/jpeg" + - boost::lexical_cast<std::string>(value) + - "-" + slice.GetOrthancInstanceId() + "_" + - boost::lexical_cast<std::string>(slice.GetFrame())); - - orthanc_.GetJsonAsync(uri, - new OrthancStone::Callable<OrthancSlicesLoader, - OrthancApiClient::JsonResponseReadyMessage> - (*this, &OrthancSlicesLoader::ParseSliceImageJpeg), - new OrthancStone::Callable<OrthancSlicesLoader, - IWebService::HttpRequestErrorMessage> - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceImage( - static_cast<unsigned int>(index), slice, quality)); - } - - - - void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index, - OrthancStone::SliceImageQuality quality) - { - if (state_ != State_GeometryReady) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - const Slice& slice = GetSlice(index); - - if (slice.HasOrthancDecoding()) - { - switch (quality) - { - case OrthancStone::SliceImageQuality_FullPng: - ScheduleSliceImagePng(slice, index); - break; - case OrthancStone::SliceImageQuality_FullPam: - ScheduleSliceImagePam(slice, index); - break; - default: - ScheduleSliceImageJpeg(slice, index, quality); - } - } - else - { - std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + - boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz"); - orthanc_.GetBinaryAsync(uri, IWebService::HttpHeaders(), - new OrthancStone::Callable<OrthancSlicesLoader, - OrthancApiClient::BinaryResponseReadyMessage> - (*this, &OrthancSlicesLoader::ParseSliceRawImage), - new OrthancStone::Callable<OrthancSlicesLoader, - IWebService::HttpRequestErrorMessage> - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceRawImage( - static_cast<unsigned int>(index), slice)); - } - } -}
--- a/Framework/Toolbox/OrthancSlicesLoader.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,209 +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 "../Messages/IObservable.h" -#include "../StoneEnumerations.h" -#include "IWebService.h" -#include "OrthancApiClient.h" -#include "SlicesSorter.h" -#include "Slice.h" - -#include <Core/Images/Image.h> - - -namespace Deprecated -{ - class OrthancSlicesLoader : public OrthancStone::IObservable, public OrthancStone::IObserver - { - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryReadyMessage, OrthancSlicesLoader); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryErrorMessage, OrthancSlicesLoader); - - - class SliceImageReadyMessage : public OrthancStone::OriginMessage<OrthancSlicesLoader> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - unsigned int sliceIndex_; - const Slice& slice_; - const Orthanc::ImageAccessor& image_; - OrthancStone::SliceImageQuality effectiveQuality_; - - public: - SliceImageReadyMessage(const OrthancSlicesLoader& origin, - unsigned int sliceIndex, - const Slice& slice, - const Orthanc::ImageAccessor& image, - OrthancStone::SliceImageQuality effectiveQuality) : - OriginMessage(origin), - sliceIndex_(sliceIndex), - slice_(slice), - image_(image), - effectiveQuality_(effectiveQuality) - { - } - - unsigned int GetSliceIndex() const - { - return sliceIndex_; - } - - const Slice& GetSlice() const - { - return slice_; - } - - const Orthanc::ImageAccessor& GetImage() const - { - return image_; - } - - OrthancStone::SliceImageQuality GetEffectiveQuality() const - { - return effectiveQuality_; - } - }; - - - class SliceImageErrorMessage : public OrthancStone::OriginMessage<OrthancSlicesLoader> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const Slice& slice_; - unsigned int sliceIndex_; - OrthancStone::SliceImageQuality effectiveQuality_; - - public: - SliceImageErrorMessage(const OrthancSlicesLoader& origin, - unsigned int sliceIndex, - const Slice& slice, - OrthancStone::SliceImageQuality effectiveQuality) : - OriginMessage(origin), - slice_(slice), - sliceIndex_(sliceIndex), - effectiveQuality_(effectiveQuality) - { - } - unsigned int GetSliceIndex() const - { - return sliceIndex_; - } - - const Slice& GetSlice() const - { - return slice_; - } - - OrthancStone::SliceImageQuality GetEffectiveQuality() const - { - return effectiveQuality_; - } - }; - - private: - enum State - { - State_Error, - State_Initialization, - State_LoadingGeometry, - State_GeometryReady - }; - - enum Mode - { - Mode_SeriesGeometry, - Mode_InstanceGeometry, - Mode_FrameGeometry, - Mode_LoadImage, - Mode_LoadRawImage, - Mode_LoadDicomFile - }; - - class Operation; - - OrthancApiClient& orthanc_; - State state_; - OrthancStone::SlicesSorter slices_; - - void NotifySliceImageSuccess(const Operation& operation, - const Orthanc::ImageAccessor& image); - - void NotifySliceImageError(const Operation& operation); - - void OnGeometryError(const IWebService::HttpRequestErrorMessage& message); - - void OnSliceImageError(const IWebService::HttpRequestErrorMessage& message); - - void ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message); - - void ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message); - - void ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message); - - void ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message); - - void ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message); - - void ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message); - - void ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message); - - void ScheduleSliceImagePng(const Slice& slice, - size_t index); - - void ScheduleSliceImagePam(const Slice& slice, - size_t index); - - void ScheduleSliceImageJpeg(const Slice& slice, - size_t index, - OrthancStone::SliceImageQuality quality); - - void SortAndFinalizeSlices(); - - public: - OrthancSlicesLoader(OrthancStone::MessageBroker& broker, - //ISliceLoaderObserver& callback, - OrthancApiClient& orthancApi); - - void ScheduleLoadSeries(const std::string& seriesId); - - void ScheduleLoadInstance(const std::string& instanceId); - - void ScheduleLoadFrame(const std::string& instanceId, - unsigned int frame); - - bool IsGeometryReady() const; - - size_t GetSlicesCount() const; - - const Slice& GetSlice(size_t index) const; - - bool LookupSlice(size_t& index, - const OrthancStone::CoordinateSystem3D& plane) const; - - void ScheduleLoadSliceImage(size_t index, - OrthancStone::SliceImageQuality requestedQuality); - }; -}
--- a/Framework/Toolbox/ParallelSlices.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +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 "ParallelSlices.h" - -#include "GeometryToolbox.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> - -namespace OrthancStone -{ - ParallelSlices::ParallelSlices() - { - LinearAlgebra::AssignVector(normal_, 0, 0, 1); - } - - - ParallelSlices::ParallelSlices(const ParallelSlices& other) - { - normal_ = other.normal_; - - slices_.resize(other.slices_.size()); - - for (size_t i = 0; i < slices_.size(); i++) - { - assert(other.slices_[i] != NULL); - slices_[i] = new CoordinateSystem3D(*other.slices_[i]); - } - } - - - ParallelSlices::~ParallelSlices() - { - for (size_t i = 0; i < slices_.size(); i++) - { - if (slices_[i] != NULL) - { - delete slices_[i]; - slices_[i] = NULL; - } - } - } - - - void ParallelSlices::AddSlice(const CoordinateSystem3D& slice) - { - if (slices_.empty()) - { - normal_ = slice.GetNormal(); - slices_.push_back(new CoordinateSystem3D(slice)); - } - else if (GeometryToolbox::IsParallel(slice.GetNormal(), normal_)) - { - slices_.push_back(new CoordinateSystem3D(slice)); - } - else - { - LOG(ERROR) << "Trying to add a slice that is not parallel to the previous ones"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - - void ParallelSlices::AddSlice(const Vector& origin, - const Vector& axisX, - const Vector& axisY) - { - CoordinateSystem3D slice(origin, axisX, axisY); - AddSlice(slice); - } - - - const CoordinateSystem3D& ParallelSlices::GetSlice(size_t index) const - { - if (index >= slices_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - return *slices_[index]; - } - } - - - bool ParallelSlices::ComputeClosestSlice(size_t& closestSlice, - double& closestDistance, - const Vector& origin) const - { - if (slices_.empty()) - { - return false; - } - - double reference = boost::numeric::ublas::inner_prod(origin, normal_); - - closestSlice = 0; - closestDistance = std::numeric_limits<double>::infinity(); - - for (size_t i = 0; i < slices_.size(); i++) - { - double distance = fabs(boost::numeric::ublas::inner_prod(slices_[i]->GetOrigin(), normal_) - reference); - - if (distance < closestDistance) - { - closestSlice = i; - closestDistance = distance; - } - } - - return true; - } - - - ParallelSlices* ParallelSlices::Reverse() const - { - std::auto_ptr<ParallelSlices> reversed(new ParallelSlices); - - for (size_t i = slices_.size(); i > 0; i--) - { - const CoordinateSystem3D& slice = *slices_[i - 1]; - - reversed->AddSlice(slice.GetOrigin(), - -slice.GetAxisX(), - slice.GetAxisY()); - } - - return reversed.release(); - } -}
--- a/Framework/Toolbox/ParallelSlices.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +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 "CoordinateSystem3D.h" - -namespace OrthancStone -{ - class ParallelSlices : public boost::noncopyable - { - private: - Vector normal_; - std::vector<CoordinateSystem3D*> slices_; - - ParallelSlices& operator= (const ParallelSlices& other); // Forbidden - - public: - ParallelSlices(); - - ParallelSlices(const ParallelSlices& other); - - ~ParallelSlices(); - - const Vector& GetNormal() const - { - return normal_; - } - - void AddSlice(const CoordinateSystem3D& slice); - - void AddSlice(const Vector& origin, - const Vector& axisX, - const Vector& axisY); - - size_t GetSliceCount() const - { - return slices_.size(); - } - - const CoordinateSystem3D& GetSlice(size_t index) const; - - bool ComputeClosestSlice(size_t& closestSlice, - double& closestDistance, - const Vector& origin) const; - - ParallelSlices* Reverse() const; - }; -}
--- a/Framework/Toolbox/ParallelSlicesCursor.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,221 +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 "ParallelSlicesCursor.h" - -#include <Core/OrthancException.h> - -namespace OrthancStone -{ - size_t ParallelSlicesCursor::GetDefaultSlice() - { - if (slices_.get() == NULL) - { - return 0; - } - else - { - return slices_->GetSliceCount() / 2; - } - } - - - size_t ParallelSlicesCursor::GetSliceCount() - { - if (slices_.get() == NULL) - { - return 0; - } - else - { - return slices_->GetSliceCount(); - } - } - - - CoordinateSystem3D ParallelSlicesCursor::GetSlice(size_t slice) - { - if (slices_.get() == NULL) - { - return CoordinateSystem3D(); - } - else - { - return slices_->GetSlice(slice); - } - } - - - void ParallelSlicesCursor::SetGeometry(const ParallelSlices& slices) - { - slices_.reset(new ParallelSlices(slices)); - - currentSlice_ = GetDefaultSlice(); - } - - - CoordinateSystem3D ParallelSlicesCursor::GetCurrentSlice() - { - if (slices_.get() != NULL && - currentSlice_ < slices_->GetSliceCount()) - { - return slices_->GetSlice(currentSlice_); - } - else - { - return CoordinateSystem3D(); // No slice is available, return the canonical geometry - } - } - - - bool ParallelSlicesCursor::SetDefaultSlice() - { - size_t slice = GetDefaultSlice(); - - if (currentSlice_ != slice) - { - currentSlice_ = slice; - return true; - } - else - { - return false; - } - } - - - bool ParallelSlicesCursor::ApplyOffset(SliceOffsetMode mode, - int offset) - { - if (slices_.get() == NULL) - { - return false; - } - - int count = static_cast<int>(slices_->GetSliceCount()); - if (count == 0) - { - return false; - } - - int slice; - if (static_cast<int>(currentSlice_) >= count) - { - slice = count - 1; - } - else - { - slice = static_cast<int>(currentSlice_); - } - - switch (mode) - { - case SliceOffsetMode_Absolute: - { - slice = offset; - break; - } - - case SliceOffsetMode_Relative: - { - slice += offset; - break; - } - - case SliceOffsetMode_Loop: - { - slice += offset; - while (slice < 0) - { - slice += count; - } - - while (slice >= count) - { - slice -= count; - } - - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - if (slice < 0) - { - slice = 0; - } - - if (slice >= count) - { - slice = count - 1; - } - - if (slice != static_cast<int>(currentSlice_)) - { - currentSlice_ = static_cast<int>(slice); - return true; - } - else - { - return false; - } - } - - - bool ParallelSlicesCursor::ApplyWheelEvent(MouseWheelDirection direction, - KeyboardModifiers modifiers) - { - int offset = (modifiers & KeyboardModifiers_Control ? 10 : 1); - - switch (direction) - { - case MouseWheelDirection_Down: - return ApplyOffset(SliceOffsetMode_Relative, -offset); - - case MouseWheelDirection_Up: - return ApplyOffset(SliceOffsetMode_Relative, offset); - - default: - return false; - } - } - - - bool ParallelSlicesCursor::LookupSliceContainingPoint(const Vector& p) - { - size_t slice; - double distance; - - if (slices_.get() != NULL && - slices_->ComputeClosestSlice(slice, distance, p)) - { - if (currentSlice_ != slice) - { - currentSlice_ = slice; - return true; - } - } - - return false; - } -}
--- a/Framework/Toolbox/ParallelSlicesCursor.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +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 "ParallelSlices.h" -#include "../StoneEnumerations.h" - -namespace OrthancStone -{ - class ParallelSlicesCursor : public boost::noncopyable - { - private: - std::auto_ptr<ParallelSlices> slices_; - size_t currentSlice_; - - size_t GetDefaultSlice(); - - public: - ParallelSlicesCursor() : - currentSlice_(0) - { - } - - void SetGeometry(const ParallelSlices& slices); - - size_t GetSliceCount(); - - CoordinateSystem3D GetSlice(size_t slice); - - CoordinateSystem3D GetCurrentSlice(); - - // Returns "true" iff. the slice has actually changed - bool SetDefaultSlice(); - - // Returns "true" iff. the slice has actually changed - bool ApplyOffset(SliceOffsetMode mode, - int offset); - - // Returns "true" iff. the slice has actually changed - bool ApplyWheelEvent(MouseWheelDirection direction, - KeyboardModifiers modifiers); - - // Returns "true" iff. the slice has actually changed - bool LookupSliceContainingPoint(const Vector& p); - }; -}
--- a/Framework/Toolbox/ShearWarpProjectiveTransform.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Toolbox/ShearWarpProjectiveTransform.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -343,6 +343,7 @@ float& maxValue, const Matrix& M_view, const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, double pixelSpacing, unsigned int countSlices, ImageInterpolation shearInterpolation, @@ -385,8 +386,8 @@ // Compute the "world" matrix that maps the source volume to the // (0,0,0)->(1,1,1) unit cube - Vector origin = source.GetGeometry().GetCoordinates(0, 0, 0); - Vector ps = source.GetGeometry().GetVoxelDimensions(VolumeProjection_Axial); + Vector origin = geometry.GetCoordinates(0, 0, 0); + Vector ps = geometry.GetVoxelDimensions(VolumeProjection_Axial); Matrix world = LinearAlgebra::Product( GeometryToolbox::CreateScalingMatrix(1.0 / ps[0], 1.0 / ps[1], 1.0 / ps[2]), GeometryToolbox::CreateTranslationMatrix(-origin[0], -origin[1], -origin[2])); @@ -598,6 +599,7 @@ float& maxValue, const Matrix& M_view, const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, bool mip, double pixelSpacing, unsigned int countSlices, @@ -607,13 +609,13 @@ if (mip) { ApplyAxialInternal<SourceFormat, TargetFormat, true> - (target, maxValue, M_view, source, pixelSpacing, + (target, maxValue, M_view, source, geometry, pixelSpacing, countSlices, shearInterpolation, warpInterpolation); } else { ApplyAxialInternal<SourceFormat, TargetFormat, false> - (target, maxValue, M_view, source, pixelSpacing, + (target, maxValue, M_view, source, geometry, pixelSpacing, countSlices, shearInterpolation, warpInterpolation); } } @@ -623,6 +625,7 @@ ShearWarpProjectiveTransform::ApplyAxial(float& maxValue, const Matrix& M_view, const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, Orthanc::PixelFormat targetFormat, unsigned int targetWidth, unsigned int targetHeight, @@ -640,7 +643,7 @@ { ApplyAxialInternal2<Orthanc::PixelFormat_Grayscale16, Orthanc::PixelFormat_Grayscale16> - (*target, maxValue, M_view, source, mip, pixelSpacing, + (*target, maxValue, M_view, source, geometry, mip, pixelSpacing, countSlices, shearInterpolation, warpInterpolation); } else if (source.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16 && @@ -648,7 +651,7 @@ { ApplyAxialInternal2<Orthanc::PixelFormat_SignedGrayscale16, Orthanc::PixelFormat_SignedGrayscale16> - (*target, maxValue, M_view, source, mip, pixelSpacing, + (*target, maxValue, M_view, source, geometry, mip, pixelSpacing, countSlices, shearInterpolation, warpInterpolation); } else
--- a/Framework/Toolbox/ShearWarpProjectiveTransform.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Toolbox/ShearWarpProjectiveTransform.h Mon Jun 24 14:35:00 2019 +0200 @@ -92,6 +92,7 @@ static Orthanc::ImageAccessor* ApplyAxial(float& maxValue, const Matrix& M_view, // cf. "CalibrateView()" const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, Orthanc::PixelFormat targetFormat, unsigned int targetWidth, unsigned int targetHeight,
--- a/Framework/Toolbox/Slice.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,367 +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 "Slice.h" - -#include "../StoneEnumerations.h" -#include "GeometryToolbox.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> -#include <Core/Toolbox.h> - -#include <boost/lexical_cast.hpp> - -namespace Deprecated -{ - static bool ParseDouble(double& target, - const std::string& source) - { - try - { - target = boost::lexical_cast<double>(source); - return true; - } - catch (boost::bad_lexical_cast&) - { - return false; - } - } - - Slice* Slice::Clone() const - { - std::auto_ptr<Slice> target(new Slice()); - - target->type_ = type_; - target->orthancInstanceId_ = orthancInstanceId_; - target->sopClassUid_ = sopClassUid_; - target->frame_ = frame_; - target->frameCount_ = frameCount_; - target->geometry_ = geometry_; - target->pixelSpacingX_ = pixelSpacingX_; - target->pixelSpacingY_ = pixelSpacingY_; - target->thickness_ = thickness_; - target->width_ = width_; - target->height_ = height_; - target->converter_ = converter_; - if (imageInformation_.get() != NULL) - target->imageInformation_.reset(imageInformation_->Clone()); - - return target.release(); - } - - bool Slice::ComputeRTDoseGeometry(const Orthanc::DicomMap& dataset, - unsigned int frame) - { - // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html - - { - std::string increment; - - if (dataset.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) - { - Orthanc::Toolbox::ToUpperCase(increment); - if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag - { - LOG(ERROR) << "Bad value for the \"FrameIncrementPointer\" tag"; - return false; - } - } - } - - std::string offsetTag; - - if (!dataset.CopyToString(offsetTag, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, false) || - offsetTag.empty()) - { - LOG(ERROR) << "Cannot read the \"GridFrameOffsetVector\" tag, check you are using Orthanc >= 1.3.1"; - return false; - } - - std::vector<std::string> offsets; - Orthanc::Toolbox::TokenizeString(offsets, offsetTag, '\\'); - - if (frameCount_ <= 1 || - offsets.size() < frameCount_ || - offsets.size() < 2 || - frame >= frameCount_) - { - LOG(ERROR) << "No information about the 3D location of some slice(s) in a RT DOSE"; - return false; - } - - double offset0, offset1, z; - - if (!ParseDouble(offset0, offsets[0]) || - !ParseDouble(offset1, offsets[1]) || - !ParseDouble(z, offsets[frame])) - { - LOG(ERROR) << "Invalid syntax"; - return false; - } - - if (!OrthancStone::LinearAlgebra::IsCloseToZero(offset0)) - { - LOG(ERROR) << "Invalid syntax"; - return false; - } - - geometry_ = OrthancStone::CoordinateSystem3D(geometry_.GetOrigin() + z * geometry_.GetNormal(), - //+ 650 * geometry_.GetAxisX(), - geometry_.GetAxisX(), - geometry_.GetAxisY()); - - thickness_ = offset1 - offset0; - if (thickness_ < 0) - { - thickness_ = -thickness_; - } - - return true; - } - - - bool Slice::ParseOrthancFrame(const Orthanc::DicomMap& dataset, - const std::string& instanceId, - unsigned int frame) - { - orthancInstanceId_ = instanceId; - frame_ = frame; - type_ = Type_OrthancDecodableFrame; - imageInformation_.reset(new Orthanc::DicomImageInformation(dataset)); - - if (!dataset.CopyToString(sopClassUid_, Orthanc::DICOM_TAG_SOP_CLASS_UID, false) || - sopClassUid_.empty()) - { - LOG(ERROR) << "Instance without a SOP class UID"; - return false; - } - - if (!dataset.ParseUnsignedInteger32(frameCount_, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) - { - frameCount_ = 1; // Assume instance with one frame - } - - if (frame >= frameCount_) - { - return false; - } - - if (!dataset.ParseUnsignedInteger32(width_, Orthanc::DICOM_TAG_COLUMNS) || - !dataset.ParseUnsignedInteger32(height_, Orthanc::DICOM_TAG_ROWS)) - { - return false; - } - - thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); - - std::string tmp; - if (dataset.CopyToString(tmp, Orthanc::DICOM_TAG_SLICE_THICKNESS, false)) - { - if (!tmp.empty() && - !ParseDouble(thickness_, tmp)) - { - return false; // Syntax error - } - } - - converter_.ReadParameters(dataset); - - OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dataset); - - std::string position, orientation; - if (dataset.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && - dataset.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) - { - geometry_ = OrthancStone::CoordinateSystem3D(position, orientation); - - bool ok = true; - - switch (OrthancStone::StringToSopClassUid(sopClassUid_)) - { - case OrthancStone::SopClassUid_RTDose: - type_ = Type_OrthancRawFrame; - ok = ComputeRTDoseGeometry(dataset, frame); - break; - - default: - break; - } - - if (!ok) - { - LOG(ERROR) << "Cannot deduce the 3D location of frame " << frame - << " in instance " << instanceId << ", whose SOP class UID is: " << sopClassUid_; - return false; - } - } - - return true; - } - - - const std::string Slice::GetOrthancInstanceId() const - { - if (type_ == Type_OrthancDecodableFrame || - type_ == Type_OrthancRawFrame) - { - return orthancInstanceId_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - - unsigned int Slice::GetFrame() const - { - if (type_ == Type_Invalid) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return frame_; - } - - - const OrthancStone::CoordinateSystem3D& Slice::GetGeometry() const - { - if (type_ == Type_Invalid) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return geometry_; - } - - - double Slice::GetThickness() const - { - if (type_ == Type_Invalid) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return thickness_; - } - - - double Slice::GetPixelSpacingX() const - { - if (type_ == Type_Invalid) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return pixelSpacingX_; - } - - - double Slice::GetPixelSpacingY() const - { - if (type_ == Type_Invalid) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return pixelSpacingY_; - } - - - unsigned int Slice::GetWidth() const - { - if (type_ == Type_Invalid) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return width_; - } - - - unsigned int Slice::GetHeight() const - { - if (type_ == Type_Invalid) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return height_; - } - - - const DicomFrameConverter& Slice::GetConverter() const - { - if (type_ == Type_Invalid) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return converter_; - } - - - bool Slice::ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const - { - if (type_ == Type_Invalid) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - bool opposite; - return (OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, - GetGeometry().GetNormal(), - plane.GetNormal()) && - OrthancStone::LinearAlgebra::IsNear(GetGeometry().ProjectAlongNormal(GetGeometry().GetOrigin()), - GetGeometry().ProjectAlongNormal(plane.GetOrigin()), - thickness_ / 2.0)); - } - - - void Slice::GetExtent(std::vector<OrthancStone::Vector>& points) const - { - double sx = GetPixelSpacingX(); - double sy = GetPixelSpacingY(); - double w = static_cast<double>(GetWidth()); - double h = static_cast<double>(GetHeight()); - - points.clear(); - points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5 * sx, -0.5 * sy)); - points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, -0.5 * sy)); - points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5 * sx, (h - 0.5) * sy)); - points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, (h - 0.5) * sy)); - } - - - const Orthanc::DicomImageInformation& Slice::GetImageInformation() const - { - if (imageInformation_.get() == NULL) - { - // Only available if constructing the "Slice" object with a DICOM map - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - return *imageInformation_; - } - } -}
--- a/Framework/Toolbox/Slice.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +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 "CoordinateSystem3D.h" -#include "DicomFrameConverter.h" - -#include <Core/DicomFormat/DicomImageInformation.h> -#include <Core/IDynamicObject.h> - -namespace Deprecated -{ - // TODO - Remove this class - class Slice : - public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */ - { - private: - enum Type - { - Type_Invalid, - Type_Standalone, - Type_OrthancDecodableFrame, - Type_OrthancRawFrame - // TODO A slice could come from some DICOM file (URL) - }; - - bool ComputeRTDoseGeometry(const Orthanc::DicomMap& dataset, - unsigned int frame); - - Type type_; - std::string orthancInstanceId_; - std::string sopClassUid_; - unsigned int frame_; - unsigned int frameCount_; // TODO : Redundant with "imageInformation_" - OrthancStone::CoordinateSystem3D geometry_; - double pixelSpacingX_; - double pixelSpacingY_; - double thickness_; - unsigned int width_; // TODO : Redundant with "imageInformation_" - unsigned int height_; // TODO : Redundant with "imageInformation_" - DicomFrameConverter converter_; // TODO : Partially redundant with "imageInformation_" - - std::auto_ptr<Orthanc::DicomImageInformation> imageInformation_; - - public: - Slice() : - type_(Type_Invalid) - { - } - - - // this constructor is used to reference, i.e, a slice that is being loaded - Slice(const std::string& orthancInstanceId, - unsigned int frame) : - type_(Type_Invalid), - orthancInstanceId_(orthancInstanceId), - frame_(frame) - { - } - - // TODO Is this constructor the best way to go to tackle missing - // layers within SliceViewerWidget? - Slice(const OrthancStone::CoordinateSystem3D& plane, - double thickness) : - type_(Type_Standalone), - frame_(0), - frameCount_(0), - geometry_(plane), - pixelSpacingX_(1), - pixelSpacingY_(1), - thickness_(thickness), - width_(0), - height_(0) - { - } - - Slice(const OrthancStone::CoordinateSystem3D& plane, - double pixelSpacingX, - double pixelSpacingY, - double thickness, - unsigned int width, - unsigned int height, - const DicomFrameConverter& converter) : - type_(Type_Standalone), - frameCount_(1), - geometry_(plane), - pixelSpacingX_(pixelSpacingX), - pixelSpacingY_(pixelSpacingY), - thickness_(thickness), - width_(width), - height_(height), - converter_(converter) - { - } - - bool IsValid() const - { - return type_ != Type_Invalid; - } - - bool ParseOrthancFrame(const Orthanc::DicomMap& dataset, - const std::string& instanceId, - unsigned int frame); - - bool HasOrthancDecoding() const - { - return type_ == Type_OrthancDecodableFrame; - } - - const std::string GetOrthancInstanceId() const; - - unsigned int GetFrame() const; - - const OrthancStone::CoordinateSystem3D& GetGeometry() const; - - double GetThickness() const; - - double GetPixelSpacingX() const; - - double GetPixelSpacingY() const; - - unsigned int GetWidth() const; - - unsigned int GetHeight() const; - - const DicomFrameConverter& GetConverter() const; - - bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const; - - void GetExtent(std::vector<OrthancStone::Vector>& points) const; - - const Orthanc::DicomImageInformation& GetImageInformation() const; - - Slice* Clone() const; - }; -}
--- a/Framework/Toolbox/SlicesSorter.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Toolbox/SlicesSorter.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -271,7 +271,7 @@ assert(slices_[i] != NULL); double tmp; - if (CoordinateSystem3D::GetDistance(tmp, slices_[i]->GetGeometry(), slice)) + if (CoordinateSystem3D::ComputeDistance(tmp, slices_[i]->GetGeometry(), slice)) { if (!found || tmp < distance)
--- a/Framework/Toolbox/ViewportGeometry.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,217 +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 "ViewportGeometry.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> - -#include <boost/math/special_functions/round.hpp> - -namespace Deprecated -{ - void ViewportGeometry::ComputeTransform() - { - // The following lines must be read in reverse order! - cairo_matrix_t tmp; - - // Bring the center of the scene to the center of the view - cairo_matrix_init_translate(&transform_, - panX_ + static_cast<double>(width_) / 2.0, - panY_ + static_cast<double>(height_) / 2.0); - - // Apply the zoom around (0,0) - cairo_matrix_init_scale(&tmp, zoom_, zoom_); - cairo_matrix_multiply(&transform_, &tmp, &transform_); - - // Bring the center of the scene to (0,0) - cairo_matrix_init_translate(&tmp, - -(sceneExtent_.GetX1() + sceneExtent_.GetX2()) / 2.0, - -(sceneExtent_.GetY1() + sceneExtent_.GetY2()) / 2.0); - cairo_matrix_multiply(&transform_, &tmp, &transform_); - } - - - ViewportGeometry::ViewportGeometry() - { - width_ = 0; - height_ = 0; - - zoom_ = 1; - panX_ = 0; - panY_ = 0; - - ComputeTransform(); - } - - - void ViewportGeometry::SetDisplaySize(unsigned int width, - unsigned int height) - { - if (width_ != width || - height_ != height) - { - LOG(INFO) << "New display size: " << width << "x" << height; - - width_ = width; - height_ = height; - - ComputeTransform(); - } - } - - - void ViewportGeometry::SetSceneExtent(const OrthancStone::Extent2D& extent) - { - LOG(INFO) << "New scene extent: (" - << extent.GetX1() << "," << extent.GetY1() << ") => (" - << extent.GetX2() << "," << extent.GetY2() << ")"; - - sceneExtent_ = extent; - ComputeTransform(); - } - - - void ViewportGeometry::MapDisplayToScene(double& sceneX /* out */, - double& sceneY /* out */, - double x, - double y) const - { - cairo_matrix_t transform = transform_; - - if (cairo_matrix_invert(&transform) != CAIRO_STATUS_SUCCESS) - { - LOG(ERROR) << "Cannot invert singular matrix"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - sceneX = x; - sceneY = y; - cairo_matrix_transform_point(&transform, &sceneX, &sceneY); - } - - - void ViewportGeometry::MapSceneToDisplay(int& displayX /* out */, - int& displayY /* out */, - double x, - double y) const - { - cairo_matrix_transform_point(&transform_, &x, &y); - - displayX = static_cast<int>(boost::math::iround(x)); - displayY = static_cast<int>(boost::math::iround(y)); - } - - - 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, - static_cast<int>(displayTouches[t].x), - static_cast<int>(displayTouches[t].y)); - - sceneTouches.push_back(Touch((float)sceneX, (float)sceneY)); - } - } - - void ViewportGeometry::MapPixelCenterToScene(double& sceneX, - double& sceneY, - int x, - int y) const - { - // Take the center of the pixel - MapDisplayToScene(sceneX, sceneY, - static_cast<double>(x) + 0.5, - static_cast<double>(y) + 0.5); - } - - - void ViewportGeometry::FitContent() - { - if (width_ > 0 && - height_ > 0 && - !sceneExtent_.IsEmpty()) - { - double zoomX = static_cast<double>(width_) / (sceneExtent_.GetX2() - sceneExtent_.GetX1()); - double zoomY = static_cast<double>(height_) / (sceneExtent_.GetY2() - sceneExtent_.GetY1()); - zoom_ = zoomX < zoomY ? zoomX : zoomY; - - panX_ = 0; - panY_ = 0; - - ComputeTransform(); - } - } - - - void ViewportGeometry::ApplyTransform(OrthancStone::CairoContext& context) const - { - cairo_set_matrix(context.GetObject(), &transform_); - } - - - void ViewportGeometry::GetPan(double& x, - double& y) const - { - x = panX_; - y = panY_; - } - - - void ViewportGeometry::SetPan(double x, - double y) - { - panX_ = x; - panY_ = y; - ComputeTransform(); - } - - - void ViewportGeometry::SetZoom(double zoom) - { - zoom_ = zoom; - ComputeTransform(); - } - - - OrthancStone::Matrix ViewportGeometry::GetMatrix() const - { - OrthancStone::Matrix m(3, 3); - - m(0, 0) = transform_.xx; - m(0, 1) = transform_.xy; - m(0, 2) = transform_.x0; - m(1, 0) = transform_.yx; - m(1, 1) = transform_.yy; - m(1, 2) = transform_.y0; - m(2, 0) = 0; - m(2, 1) = 0; - m(2, 2) = 1; - - return m; - } -}
--- a/Framework/Toolbox/ViewportGeometry.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +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 "../Viewport/CairoContext.h" -#include "Extent2D.h" -#include "LinearAlgebra.h" -#include "../Viewport/IMouseTracker.h" // to include "Touch" definition - -namespace Deprecated -{ - class ViewportGeometry - { - private: - // Extent of the scene (in world units) - OrthancStone::Extent2D sceneExtent_; - - // Size of the display (in pixels) - unsigned int width_; - unsigned int height_; - - // Zoom/pan - double zoom_; - double panX_; // In pixels (display units) - double panY_; - - cairo_matrix_t transform_; // Scene-to-display transformation - - void ComputeTransform(); - - public: - ViewportGeometry(); - - void SetDisplaySize(unsigned int width, - unsigned int height); - - void SetSceneExtent(const OrthancStone::Extent2D& extent); - - const OrthancStone::Extent2D& GetSceneExtent() const - { - return sceneExtent_; - } - - void MapDisplayToScene(double& sceneX /* out */, - double& sceneY /* out */, - double x, - double y) const; - - void MapPixelCenterToScene(double& sceneX /* out */, - double& sceneY /* out */, - 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, - double y) const; - - unsigned int GetDisplayWidth() const - { - return width_; - } - - unsigned int GetDisplayHeight() const - { - return height_; - } - - double GetZoom() const - { - return zoom_; - } - - void FitContent(); - - void ApplyTransform(OrthancStone::CairoContext& context) const; - - void GetPan(double& x, - double& y) const; - - void SetPan(double x, - double y); - - void SetZoom(double zoom); - - OrthancStone::Matrix GetMatrix() const; - }; -}
--- a/Framework/Toolbox/VolumeImageGeometry.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,309 +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 "VolumeImageGeometry.h" - -#include "../Toolbox/GeometryToolbox.h" - -#include <Core/OrthancException.h> - - -namespace OrthancStone -{ - void VolumeImageGeometry::Invalidate() - { - Vector p = (axialGeometry_.GetOrigin() + - static_cast<double>(depth_ - 1) * voxelDimensions_[2] * axialGeometry_.GetNormal()); - - coronalGeometry_ = CoordinateSystem3D(p, - axialGeometry_.GetAxisX(), - -axialGeometry_.GetNormal()); - - sagittalGeometry_ = CoordinateSystem3D(p, - axialGeometry_.GetAxisY(), - axialGeometry_.GetNormal()); - - Vector origin = ( - axialGeometry_.MapSliceToWorldCoordinates(-0.5 * voxelDimensions_[0], - -0.5 * voxelDimensions_[1]) - - 0.5 * voxelDimensions_[2] * axialGeometry_.GetNormal()); - - Vector scaling; - - if (width_ == 0 || - height_ == 0 || - depth_ == 0) - { - LinearAlgebra::AssignVector(scaling, 1, 1, 1); - } - else - { - scaling = ( - axialGeometry_.GetAxisX() * voxelDimensions_[0] * static_cast<double>(width_) + - axialGeometry_.GetAxisY() * voxelDimensions_[1] * static_cast<double>(height_) + - axialGeometry_.GetNormal() * voxelDimensions_[2] * static_cast<double>(depth_)); - } - - transform_ = LinearAlgebra::Product( - GeometryToolbox::CreateTranslationMatrix(origin[0], origin[1], origin[2]), - GeometryToolbox::CreateScalingMatrix(scaling[0], scaling[1], scaling[2])); - - LinearAlgebra::InvertMatrix(transformInverse_, transform_); - } - - - VolumeImageGeometry::VolumeImageGeometry() : - width_(0), - height_(0), - depth_(0) - { - LinearAlgebra::AssignVector(voxelDimensions_, 1, 1, 1); - Invalidate(); - } - - - void VolumeImageGeometry::SetSize(unsigned int width, - unsigned int height, - unsigned int depth) - { - width_ = width; - height_ = height; - depth_ = depth; - Invalidate(); - } - - - void VolumeImageGeometry::SetAxialGeometry(const CoordinateSystem3D& geometry) - { - axialGeometry_ = geometry; - Invalidate(); - } - - - void VolumeImageGeometry::SetVoxelDimensions(double x, - double y, - double z) - { - if (x <= 0 || - y <= 0 || - z <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - LinearAlgebra::AssignVector(voxelDimensions_, x, y, z); - Invalidate(); - } - } - - - const CoordinateSystem3D& VolumeImageGeometry::GetProjectionGeometry(VolumeProjection projection) const - { - switch (projection) - { - case VolumeProjection_Axial: - return axialGeometry_; - - case VolumeProjection_Coronal: - return coronalGeometry_; - - case VolumeProjection_Sagittal: - return sagittalGeometry_; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - - Vector VolumeImageGeometry::GetVoxelDimensions(VolumeProjection projection) const - { - switch (projection) - { - case VolumeProjection_Axial: - return voxelDimensions_; - - case VolumeProjection_Coronal: - return LinearAlgebra::CreateVector(voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]); - - case VolumeProjection_Sagittal: - return LinearAlgebra::CreateVector(voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]); - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - - unsigned int VolumeImageGeometry::GetProjectionWidth(VolumeProjection projection) const - { - switch (projection) - { - case VolumeProjection_Axial: - return width_; - - case VolumeProjection_Coronal: - return width_; - - case VolumeProjection_Sagittal: - return height_; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - - unsigned int VolumeImageGeometry::GetProjectionHeight(VolumeProjection projection) const - { - switch (projection) - { - case VolumeProjection_Axial: - return height_; - - case VolumeProjection_Coronal: - return depth_; - - case VolumeProjection_Sagittal: - return depth_; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - - unsigned int VolumeImageGeometry::GetProjectionDepth(VolumeProjection projection) const - { - switch (projection) - { - case VolumeProjection_Axial: - return depth_; - - case VolumeProjection_Coronal: - return height_; - - case VolumeProjection_Sagittal: - return width_; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - - Vector VolumeImageGeometry::GetCoordinates(float x, - float y, - float z) const - { - Vector p = LinearAlgebra::Product(transform_, LinearAlgebra::CreateVector(x, y, z, 1)); - - assert(LinearAlgebra::IsNear(p[3], 1)); // Affine transform, no perspective effect - - // Back to non-homogeneous coordinates - return LinearAlgebra::CreateVector(p[0], p[1], p[2]); - } - - - bool VolumeImageGeometry::DetectProjection(VolumeProjection& projection, - const Vector& planeNormal) const - { - if (GeometryToolbox::IsParallel(planeNormal, axialGeometry_.GetNormal())) - { - projection = VolumeProjection_Axial; - return true; - } - else if (GeometryToolbox::IsParallel(planeNormal, coronalGeometry_.GetNormal())) - { - projection = VolumeProjection_Coronal; - return true; - } - else if (GeometryToolbox::IsParallel(planeNormal, sagittalGeometry_.GetNormal())) - { - projection = VolumeProjection_Sagittal; - return true; - } - else - { - return false; - } - } - - - bool VolumeImageGeometry::DetectSlice(VolumeProjection& projection, - unsigned int& slice, - const CoordinateSystem3D& plane) const - { - if (!DetectProjection(projection, plane.GetNormal())) - { - return false; - } - - // Transforms the coordinates of the origin of the plane, into the - // coordinates of the axial geometry - const Vector& origin = plane.GetOrigin(); - Vector p = LinearAlgebra::Product( - transformInverse_, - LinearAlgebra::CreateVector(origin[0], origin[1], origin[2], 1)); - - assert(LinearAlgebra::IsNear(p[3], 1)); - - double z; - - switch (projection) - { - case VolumeProjection_Axial: - z = p[2]; - break; - - case VolumeProjection_Coronal: - z = p[1]; - break; - - case VolumeProjection_Sagittal: - z = p[0]; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - const unsigned int projectionDepth = GetProjectionDepth(projection); - - z *= static_cast<double>(projectionDepth); - if (z < 0) - { - return false; - } - - unsigned int d = static_cast<unsigned int>(std::floor(z)); - if (d >= projectionDepth) - { - return false; - } - else - { - slice = d; - return true; - } - } -}
--- a/Framework/Toolbox/VolumeImageGeometry.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +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 "../StoneEnumerations.h" -#include "CoordinateSystem3D.h" - -namespace OrthancStone -{ - class VolumeImageGeometry - { - private: - unsigned int width_; - unsigned int height_; - unsigned int depth_; - CoordinateSystem3D axialGeometry_; - CoordinateSystem3D coronalGeometry_; - CoordinateSystem3D sagittalGeometry_; - Vector voxelDimensions_; - Matrix transform_; - Matrix transformInverse_; - - void Invalidate(); - - public: - VolumeImageGeometry(); - - unsigned int GetWidth() const - { - return width_; - } - - unsigned int GetHeight() const - { - return height_; - } - - unsigned int GetDepth() const - { - return depth_; - } - - const CoordinateSystem3D& GetAxialGeometry() const - { - return axialGeometry_; - } - - const CoordinateSystem3D& GetCoronalGeometry() const - { - return coronalGeometry_; - } - - const CoordinateSystem3D& GetSagittalGeometry() const - { - return sagittalGeometry_; - } - - const CoordinateSystem3D& GetProjectionGeometry(VolumeProjection projection) const; - - const Matrix& GetTransform() const - { - return transform_; - } - - const Matrix& GetTransformInverse() const - { - return transformInverse_; - } - - void SetSize(unsigned int width, - unsigned int height, - unsigned int depth); - - // Set the geometry of the first axial slice (i.e. the one whose - // depth == 0) - void SetAxialGeometry(const CoordinateSystem3D& geometry); - - void SetVoxelDimensions(double x, - double y, - double z); - - Vector GetVoxelDimensions(VolumeProjection projection) const; - - unsigned int GetProjectionWidth(VolumeProjection projection) const; - - unsigned int GetProjectionHeight(VolumeProjection projection) const; - - unsigned int GetProjectionDepth(VolumeProjection projection) const; - - // Get the 3D position of a point in the volume, where x, y and z - // lie in the [0;1] range - Vector GetCoordinates(float x, - float y, - float z) const; - - bool DetectProjection(VolumeProjection& projection, - const Vector& planeNormal) const; - - bool DetectSlice(VolumeProjection& projection, - unsigned int& slice, - const CoordinateSystem3D& plane) const; - }; -}
--- a/Framework/Viewport/CairoContext.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,146 +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 "CairoContext.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> - - -namespace OrthancStone -{ - CairoContext::CairoContext(CairoSurface& surface) : - width_(surface.GetWidth()), - height_(surface.GetHeight()) - { - context_ = cairo_create(surface.GetObject()); - if (!context_) - { - LOG(ERROR) << "Cannot create Cairo drawing context"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - CairoContext::~CairoContext() - { - if (context_ != NULL) - { - cairo_destroy(context_); - context_ = NULL; - } - } - - - void CairoContext::SetSourceColor(uint8_t red, - uint8_t green, - uint8_t blue) - { - cairo_set_source_rgb(context_, - static_cast<float>(red) / 255.0f, - static_cast<float>(green) / 255.0f, - static_cast<float>(blue) / 255.0f); - } - - - class CairoContext::AlphaSurface : public boost::noncopyable - { - private: - cairo_surface_t *surface_; - - public: - AlphaSurface(unsigned int width, - unsigned int height) - { - surface_ = cairo_image_surface_create(CAIRO_FORMAT_A8, width, height); - - if (!surface_) - { - // Should never occur - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS) - { - LOG(ERROR) << "Cannot create a Cairo surface"; - cairo_surface_destroy(surface_); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - ~AlphaSurface() - { - cairo_surface_destroy(surface_); - } - - void GetAccessor(Orthanc::ImageAccessor& target) - { - target.AssignWritable(Orthanc::PixelFormat_Grayscale8, - cairo_image_surface_get_width(surface_), - cairo_image_surface_get_height(surface_), - cairo_image_surface_get_stride(surface_), - cairo_image_surface_get_data(surface_)); - } - - void Blit(cairo_t* cr, - double x, - double y) - { - cairo_surface_mark_dirty(surface_); - cairo_mask_surface(cr, surface_, x, y); - cairo_fill(cr); - } - }; - - - void CairoContext::DrawText(const Orthanc::Font& font, - const std::string& text, - double x, - double y, - BitmapAnchor anchor) - { - // Render a bitmap containing the text - unsigned int width, height; - font.ComputeTextExtent(width, height, text); - - AlphaSurface surface(width, height); - - Orthanc::ImageAccessor accessor; - surface.GetAccessor(accessor); - font.Draw(accessor, text, 0, 0, 255); - - // Correct the text location given the anchor location - double deltaX, deltaY; - ComputeAnchorTranslation(deltaX, deltaY, anchor, width, height); - - // Cancel zoom/rotation before blitting the text onto the surface - double pixelX = x; - double pixelY = y; - cairo_user_to_device(context_, &pixelX, &pixelY); - - cairo_save(context_); - cairo_identity_matrix(context_); - - // Blit the text bitmap - surface.Blit(context_, pixelX + deltaX, pixelY + deltaY); - cairo_restore(context_); - } -}
--- a/Framework/Viewport/CairoContext.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +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 "CairoSurface.h" -#include "../StoneEnumerations.h" - -#include <Core/Images/Font.h> - -namespace OrthancStone -{ - // This is a RAII wrapper around the Cairo drawing context - class CairoContext : public boost::noncopyable - { - private: - class AlphaSurface; - - cairo_t* context_; - unsigned int width_; - unsigned int height_; - - public: - CairoContext(CairoSurface& surface); - - ~CairoContext(); - - cairo_t* GetObject() - { - return context_; - } - - unsigned int GetWidth() const - { - return width_; - } - - unsigned int GetHeight() const - { - return height_; - } - - void SetSourceColor(uint8_t red, - uint8_t green, - uint8_t blue); - - void SetSourceColor(const uint8_t color[3]) - { - SetSourceColor(color[0], color[1], color[2]); - } - - void DrawText(const Orthanc::Font& font, - const std::string& text, - double x, - double y, - BitmapAnchor anchor); - }; -}
--- a/Framework/Viewport/CairoFont.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +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 "CairoFont.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> - -namespace OrthancStone -{ - CairoFont::CairoFont(const char* family, - cairo_font_slant_t slant, - cairo_font_weight_t weight) - { - font_ = cairo_toy_font_face_create(family, slant, weight); - if (font_ == NULL) - { - LOG(ERROR) << "Unknown font: " << family; - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); - } - } - - - CairoFont::~CairoFont() - { - if (font_ != NULL) - { - cairo_font_face_destroy(font_); - } - } - - - void CairoFont::Draw(CairoContext& context, - const std::string& text, - double size) - { - if (size <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - cairo_t* cr = context.GetObject(); - cairo_set_font_face(cr, font_); - cairo_set_font_size(cr, size); - cairo_show_text(cr, text.c_str()); - } -}
--- a/Framework/Viewport/CairoFont.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +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 - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error The class CairoFont cannot be used in sandboxed environments -#endif - -#include "CairoContext.h" - -namespace OrthancStone -{ - class CairoFont : public boost::noncopyable - { - private: - cairo_font_face_t* font_; - - public: - CairoFont(const char* family, - cairo_font_slant_t slant, - cairo_font_weight_t weight); - - ~CairoFont(); - - void Draw(CairoContext& context, - const std::string& text, - double size); - }; -}
--- a/Framework/Viewport/CairoSurface.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +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 "CairoSurface.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> -#include <Core/Images/ImageProcessing.h> - -namespace OrthancStone -{ - void CairoSurface::Release() - { - if (surface_) - { - cairo_surface_destroy(surface_); - surface_ = NULL; - } - } - - - void CairoSurface::Allocate(unsigned int width, - unsigned int height, - bool hasAlpha) - { - Release(); - - hasAlpha_ = hasAlpha; - - surface_ = cairo_image_surface_create - (hasAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, width, height); - if (!surface_) - { - // Should never occur - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS) - { - LOG(ERROR) << "Cannot create a Cairo surface"; - cairo_surface_destroy(surface_); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - width_ = width; - height_ = height; - pitch_ = cairo_image_surface_get_stride(surface_); - buffer_ = cairo_image_surface_get_data(surface_); - } - - - CairoSurface::CairoSurface(Orthanc::ImageAccessor& accessor, - bool hasAlpha) : - hasAlpha_(hasAlpha) - { - if (accessor.GetFormat() != Orthanc::PixelFormat_BGRA32) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - width_ = accessor.GetWidth(); - height_ = accessor.GetHeight(); - pitch_ = accessor.GetPitch(); - buffer_ = accessor.GetBuffer(); - - surface_ = cairo_image_surface_create_for_data - (reinterpret_cast<unsigned char*>(buffer_), - hasAlpha_ ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, - width_, height_, pitch_); - if (!surface_) - { - // Should never occur - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS) - { - LOG(ERROR) << "Bad pitch for a Cairo surface"; - cairo_surface_destroy(surface_); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - void CairoSurface::SetSize(unsigned int width, - unsigned int height, - bool hasAlpha) - { - if (hasAlpha_ != hasAlpha || - width_ != width || - height_ != height) - { - Allocate(width, height, hasAlpha); - } - } - - - void CairoSurface::Copy(const CairoSurface& other) - { - SetSize(other.GetWidth(), other.GetHeight(), other.HasAlpha()); - - Orthanc::ImageAccessor source, target; - - other.GetReadOnlyAccessor(source); - GetWriteableAccessor(target); - - Orthanc::ImageProcessing::Copy(target, source); - - cairo_surface_mark_dirty(surface_); - } - - - void CairoSurface::Copy(const Orthanc::ImageAccessor& source, - bool hasAlpha) - { - SetSize(source.GetWidth(), source.GetHeight(), hasAlpha); - - Orthanc::ImageAccessor target; - GetWriteableAccessor(target); - - Orthanc::ImageProcessing::Convert(target, source); - - cairo_surface_mark_dirty(surface_); - } - - - void CairoSurface::GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const - { - target.AssignReadOnly(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_); - } - - - void CairoSurface::GetWriteableAccessor(Orthanc::ImageAccessor& target) - { - target.AssignWritable(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_); - } -}
--- a/Framework/Viewport/CairoSurface.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +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 <Core/Images/ImageAccessor.h> - -#include <boost/noncopyable.hpp> -#include <cairo.h> - -namespace OrthancStone -{ - class CairoSurface : public boost::noncopyable - { - private: - cairo_surface_t* surface_; - unsigned int width_; - unsigned int height_; - unsigned int pitch_; - void* buffer_; - bool hasAlpha_; - - void Release(); - - void Allocate(unsigned int width, - unsigned int height, - bool hasAlpha); - - public: - CairoSurface() : - surface_(NULL) - { - Allocate(0, 0, false); - } - - CairoSurface(unsigned int width, - unsigned int height, - bool hasAlpha) : - surface_(NULL) - { - Allocate(width, height, hasAlpha); - } - - CairoSurface(Orthanc::ImageAccessor& accessor, - bool hasAlpha); - - ~CairoSurface() - { - Release(); - } - - void SetSize(unsigned int width, - unsigned int height, - bool hasAlpha); - - void Copy(const CairoSurface& other); - - void Copy(const Orthanc::ImageAccessor& source, - bool hasAlpha); - - unsigned int GetWidth() const - { - return width_; - } - - unsigned int GetHeight() const - { - return height_; - } - - unsigned int GetPitch() const - { - return pitch_; - } - - const void* GetBuffer() const - { - return buffer_; - } - - void* GetBuffer() - { - return buffer_; - } - - cairo_surface_t* GetObject() - { - return surface_; - } - - bool HasAlpha() const - { - return hasAlpha_; - } - - void GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const; - - void GetWriteableAccessor(Orthanc::ImageAccessor& target); - }; -}
--- a/Framework/Viewport/IMouseTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +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 "CairoSurface.h" -#include <vector> - -namespace Deprecated -{ - 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. - class IMouseTracker : public boost::noncopyable - { - public: - virtual ~IMouseTracker() - { - } - - virtual void Render(Orthanc::ImageAccessor& surface) = 0; - - virtual void MouseUp() = 0; - - // Returns "true" iff. the background scene must be repainted - virtual void MouseMove(int x, - int y, - const std::vector<Touch>& displayTouches) = 0; - }; -}
--- a/Framework/Viewport/IStatusBar.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +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 <string> -#include <boost/noncopyable.hpp> - -namespace Deprecated -{ - class IStatusBar : public boost::noncopyable - { - public: - virtual ~IStatusBar() - { - } - - virtual void ClearMessage() = 0; - - virtual void SetMessage(const std::string& message) = 0; - }; -}
--- a/Framework/Viewport/IViewport.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +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 "IStatusBar.h" -#include "../StoneEnumerations.h" -#include "../Messages/IObservable.h" - -#include <Core/Images/ImageAccessor.h> -#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition - -namespace Deprecated -{ - class IWidget; // Forward declaration - - class IViewport : public OrthancStone::IObservable - { - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ViewportChangedMessage, IViewport); - - IViewport(OrthancStone::MessageBroker& broker) : - IObservable(broker) - { - } - - virtual ~IViewport() - { - } - - virtual void FitContent() = 0; - - virtual void SetStatusBar(IStatusBar& statusBar) = 0; - - virtual void SetSize(unsigned int width, - unsigned int height) = 0; - - // The function returns "true" iff. a new frame was rendered - virtual bool Render(Orthanc::ImageAccessor& surface) = 0; - - virtual void MouseDown(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& touches) = 0; - - virtual void MouseUp() = 0; - - virtual void MouseMove(int x, - int y, - const std::vector<Touch>& displayTouches) = 0; - - virtual void MouseEnter() = 0; - - virtual void MouseLeave() = 0; - - virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers) = 0; - - virtual void KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers) = 0; - - virtual bool HasAnimation() = 0; - - virtual void DoAnimation() = 0; - - // Should only be called from IWidget - // TODO Why should this be virtual? - virtual void NotifyContentChanged() - { - BroadcastMessage(ViewportChangedMessage(*this)); - } - }; -}
--- a/Framework/Viewport/WidgetViewport.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,289 +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 "WidgetViewport.h" - -#include <Core/Images/ImageProcessing.h> -#include <Core/OrthancException.h> - -namespace Deprecated -{ - WidgetViewport::WidgetViewport(OrthancStone::MessageBroker& broker) : - IViewport(broker), - statusBar_(NULL), - isMouseOver_(false), - lastMouseX_(0), - lastMouseY_(0), - backgroundChanged_(false) - { - } - - - void WidgetViewport::FitContent() - { - if (centralWidget_.get() != NULL) - { - centralWidget_->FitContent(); - } - } - - - void WidgetViewport::SetStatusBar(IStatusBar& statusBar) - { - statusBar_ = &statusBar; - - if (centralWidget_.get() != NULL) - { - centralWidget_->SetStatusBar(statusBar); - } - } - - - IWidget& WidgetViewport::SetCentralWidget(IWidget* widget) - { - if (widget == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - mouseTracker_.reset(NULL); - - centralWidget_.reset(widget); - centralWidget_->SetViewport(*this); - - if (statusBar_ != NULL) - { - centralWidget_->SetStatusBar(*statusBar_); - } - - NotifyBackgroundChanged(); - - return *widget; - } - - - void WidgetViewport::NotifyBackgroundChanged() - { - backgroundChanged_ = true; - NotifyContentChanged(); - } - - - void WidgetViewport::SetSize(unsigned int width, - unsigned int height) - { - background_.SetSize(width, height, false /* no alpha */); - - if (centralWidget_.get() != NULL) - { - centralWidget_->SetSize(width, height); - } - - NotifyBackgroundChanged(); - } - - - bool WidgetViewport::Render(Orthanc::ImageAccessor& surface) - { - if (centralWidget_.get() == NULL) - { - return false; - } - - Orthanc::ImageAccessor background; - background_.GetWriteableAccessor(background); - - if (backgroundChanged_ && - !centralWidget_->Render(background)) - { - return false; - } - - if (background.GetWidth() != surface.GetWidth() || - background.GetHeight() != surface.GetHeight()) - { - return false; - } - - Orthanc::ImageProcessing::Convert(surface, background); - - if (mouseTracker_.get() != NULL) - { - mouseTracker_->Render(surface); - } - else if (isMouseOver_) - { - centralWidget_->RenderMouseOver(surface, lastMouseX_, lastMouseY_); - } - - return true; - } - - void WidgetViewport::TouchStart(const std::vector<Touch>& displayTouches) - { - MouseDown(OrthancStone::MouseButton_Left, (int)displayTouches[0].x, (int)displayTouches[0].y, OrthancStone::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(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& displayTouches - ) - { - lastMouseX_ = x; - lastMouseY_ = y; - - if (centralWidget_.get() != NULL) - { - mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers, displayTouches)); - } - else - { - mouseTracker_.reset(NULL); - } - - NotifyContentChanged(); - } - - - void WidgetViewport::MouseUp() - { - if (mouseTracker_.get() != NULL) - { - mouseTracker_->MouseUp(); - mouseTracker_.reset(NULL); - NotifyContentChanged(); - } - } - - - void WidgetViewport::MouseMove(int x, - int y, - const std::vector<Touch>& displayTouches) - { - if (centralWidget_.get() == NULL) - { - return; - } - - lastMouseX_ = x; - lastMouseY_ = y; - - bool repaint = false; - - if (mouseTracker_.get() != NULL) - { - mouseTracker_->MouseMove(x, y, displayTouches); - repaint = true; - } - else - { - repaint = centralWidget_->HasRenderMouseOver(); - } - - if (repaint) - { - // The scene must be repainted, notify the observers - NotifyContentChanged(); - } - } - - - void WidgetViewport::MouseEnter() - { - isMouseOver_ = true; - NotifyContentChanged(); - } - - - void WidgetViewport::MouseLeave() - { - isMouseOver_ = false; - - if (mouseTracker_.get() != NULL) - { - mouseTracker_->MouseUp(); - mouseTracker_.reset(NULL); - } - - NotifyContentChanged(); - } - - - void WidgetViewport::MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers) - { - if (centralWidget_.get() != NULL && - mouseTracker_.get() == NULL) - { - centralWidget_->MouseWheel(direction, x, y, modifiers); - } - } - - - void WidgetViewport::KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers) - { - if (centralWidget_.get() != NULL && - mouseTracker_.get() == NULL) - { - centralWidget_->KeyPressed(key, keyChar, modifiers); - } - } - - - bool WidgetViewport::HasAnimation() - { - if (centralWidget_.get() != NULL) - { - return centralWidget_->HasAnimation(); - } - else - { - return false; - } - } - - - void WidgetViewport::DoAnimation() - { - if (centralWidget_.get() != NULL) - { - centralWidget_->DoAnimation(); - } - } -}
--- a/Framework/Viewport/WidgetViewport.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +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 "IViewport.h" -#include "../Widgets/IWidget.h" - -#include <memory> - -namespace Deprecated -{ - class WidgetViewport : public IViewport - { - private: - std::auto_ptr<IWidget> centralWidget_; - IStatusBar* statusBar_; - std::auto_ptr<IMouseTracker> mouseTracker_; - bool isMouseOver_; - int lastMouseX_; - int lastMouseY_; - OrthancStone::CairoSurface background_; - bool backgroundChanged_; - - public: - WidgetViewport(OrthancStone::MessageBroker& broker); - - virtual void FitContent(); - - virtual void SetStatusBar(IStatusBar& statusBar); - - IWidget& SetCentralWidget(IWidget* widget); // Takes ownership - - virtual void NotifyBackgroundChanged(); - - virtual void SetSize(unsigned int width, - unsigned int height); - - virtual bool Render(Orthanc::ImageAccessor& surface); - - virtual void MouseDown(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& displayTouches); - - virtual void MouseUp(); - - virtual void MouseMove(int x, - 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(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers); - - virtual void KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers); - - virtual bool HasAnimation(); - - virtual void DoAnimation(); - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/DicomVolumeImage.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,94 @@ +/** + * 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 "DicomVolumeImage.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + void DicomVolumeImage::CheckHasGeometry() const + { + if (!HasGeometry()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void DicomVolumeImage::Initialize(const VolumeImageGeometry& geometry, + Orthanc::PixelFormat format) + { + geometry_.reset(new VolumeImageGeometry(geometry)); + image_.reset(new ImageBuffer3D(format, geometry_->GetWidth(), geometry_->GetHeight(), + geometry_->GetDepth(), false /* don't compute range */)); + + revision_ ++; + } + + + void DicomVolumeImage::SetDicomParameters(const DicomInstanceParameters& parameters) + { + parameters_.reset(parameters.Clone()); + revision_ ++; + } + + + bool DicomVolumeImage::HasGeometry() const + { + return (geometry_.get() != NULL && + image_.get() != NULL); + } + + + ImageBuffer3D& DicomVolumeImage::GetPixelData() + { + CheckHasGeometry(); + return *image_; + } + + + const ImageBuffer3D& DicomVolumeImage::GetPixelData() const + { + CheckHasGeometry(); + return *image_; + } + + + const VolumeImageGeometry& DicomVolumeImage::GetGeometry() const + { + CheckHasGeometry(); + return *geometry_; + } + + + const DicomInstanceParameters& DicomVolumeImage::GetDicomParameters() const + { + if (HasDicomParameters()) + { + return *parameters_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/DicomVolumeImage.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,86 @@ +/** + * 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 "../Messages/IMessage.h" +#include "../Toolbox/DicomInstanceParameters.h" +#include "ImageBuffer3D.h" +#include "VolumeImageGeometry.h" + +namespace OrthancStone +{ + /** + This class combines a 3D image buffer, a 3D volume geometry and + information about the DICOM parameters of the series. + (MPR means MultiPlanar Reconstruction) + */ + class DicomVolumeImage : public boost::noncopyable + { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, DicomVolumeImage); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentUpdatedMessage, DicomVolumeImage); + + private: + uint64_t revision_; + std::auto_ptr<VolumeImageGeometry> geometry_; + std::auto_ptr<ImageBuffer3D> image_; + std::auto_ptr<DicomInstanceParameters> parameters_; + + void CheckHasGeometry() const; + + public: + DicomVolumeImage() : + revision_(0) + { + } + + void IncrementRevision() + { + revision_ ++; + } + + void Initialize(const VolumeImageGeometry& geometry, + Orthanc::PixelFormat format); + + void SetDicomParameters(const DicomInstanceParameters& parameters); + + uint64_t GetRevision() const + { + return revision_; + } + + bool HasGeometry() const; + + ImageBuffer3D& GetPixelData(); + + const ImageBuffer3D& GetPixelData() const; + + const VolumeImageGeometry& GetGeometry() const; + + bool HasDicomParameters() const + { + return parameters_.get() != NULL; + } + + const DicomInstanceParameters& GetDicomParameters() const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,115 @@ +/** + * 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 "DicomVolumeImageMPRSlicer.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + void DicomVolumeImageMPRSlicer::Slice::CheckValid() const + { + if (!valid_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + DicomVolumeImageMPRSlicer::Slice::Slice(const DicomVolumeImage& volume, + const CoordinateSystem3D& cuttingPlane) : + volume_(volume), + revision_(volume_.GetRevision()) + { + valid_ = (volume_.HasDicomParameters() && + volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane)); + } + + + VolumeProjection DicomVolumeImageMPRSlicer::Slice::GetProjection() const + { + CheckValid(); + return projection_; + } + + + unsigned int DicomVolumeImageMPRSlicer::Slice::GetSliceIndex() const + { + CheckValid(); + return sliceIndex_; + } + + + ISceneLayer* DicomVolumeImageMPRSlicer::Slice::CreateSceneLayer(const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane) + { + CheckValid(); + + if (configurator == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer, + "A style configurator is mandatory for textures"); + } + + std::auto_ptr<TextureBaseSceneLayer> texture; + + { + const DicomInstanceParameters& parameters = volume_.GetDicomParameters(); + ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_); + texture.reset(dynamic_cast<TextureBaseSceneLayer*> + (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters))); + } + + const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_); + + double x0, y0, x1, y1; + cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); + cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX()); + texture->SetOrigin(x0, y0); + + double dx = x1 - x0; + double dy = y1 - y0; + if (!LinearAlgebra::IsCloseToZero(dx) || + !LinearAlgebra::IsCloseToZero(dy)) + { + texture->SetAngle(atan2(dy, dx)); + } + + Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_); + texture->SetPixelSpacing(tmp[0], tmp[1]); + + return texture.release(); + } + + + IVolumeSlicer::IExtractedSlice* + DicomVolumeImageMPRSlicer::ExtractSlice(const CoordinateSystem3D& cuttingPlane) + { + if (volume_->HasGeometry()) + { + return new Slice(*volume_, cuttingPlane); + } + else + { + return new IVolumeSlicer::InvalidSlice; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/DicomVolumeImageMPRSlicer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,94 @@ +/** + * 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 "DicomVolumeImage.h" +#include "IVolumeSlicer.h" + +#include <boost/shared_ptr.hpp> + +namespace OrthancStone +{ + /** + Implements the IVolumeSlicer on Dicom volume data when the cutting plane + that is supplied to the slicer is either axial, sagittal or coronal. + Arbitrary planes are *not* supported + */ + class DicomVolumeImageMPRSlicer : public IVolumeSlicer + { + public: + class Slice : public IExtractedSlice + { + private: + const DicomVolumeImage& volume_; + uint64_t revision_; + bool valid_; + VolumeProjection projection_; + unsigned int sliceIndex_; + + void CheckValid() const; + + public: + /** + Represents a slice of a volume image that is parallel to the + coordinate system axis. + The constructor initializes the type of projection (axial, sagittal or + coronal) and the corresponding slice index, from the cutting plane. + */ + Slice(const DicomVolumeImage& volume, + const CoordinateSystem3D& cuttingPlane); + + void SetRevision(uint64_t revision) + { + revision_ = revision; + } + + VolumeProjection GetProjection() const; + + unsigned int GetSliceIndex() const; + + virtual bool IsValid() + { + return valid_; + } + + virtual uint64_t GetRevision() + { + return revision_; + } + + virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane); + }; + + private: + boost::shared_ptr<DicomVolumeImage> volume_; + + public: + DicomVolumeImageMPRSlicer(const boost::shared_ptr<DicomVolumeImage>& volume) : + volume_(volume) + { + } + + virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/DicomVolumeImageReslicer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,116 @@ +/** + * 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 "DicomVolumeImageReslicer.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + class DicomVolumeImageReslicer::Slice : public IVolumeSlicer::IExtractedSlice + { + private: + DicomVolumeImageReslicer& that_; + CoordinateSystem3D cuttingPlane_; + + public: + Slice(DicomVolumeImageReslicer& that, + const CoordinateSystem3D& cuttingPlane) : + that_(that), + cuttingPlane_(cuttingPlane) + { + } + + virtual bool IsValid() + { + return true; + } + + virtual uint64_t GetRevision() + { + return that_.volume_->GetRevision(); + } + + virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane) + { + VolumeReslicer& reslicer = that_.reslicer_; + + if (configurator == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Must provide a layer style configurator"); + } + + reslicer.SetOutputFormat(that_.volume_->GetPixelData().GetFormat()); + reslicer.Apply(that_.volume_->GetPixelData(), + that_.volume_->GetGeometry(), + cuttingPlane); + + if (reslicer.IsSuccess()) + { + std::auto_ptr<TextureBaseSceneLayer> layer + (configurator->CreateTextureFromDicom(reslicer.GetOutputSlice(), + that_.volume_->GetDicomParameters())); + if (layer.get() == NULL) + { + return NULL; + } + + double s = reslicer.GetPixelSpacing(); + layer->SetPixelSpacing(s, s); + layer->SetOrigin(reslicer.GetOutputExtent().GetX1() + 0.5 * s, + reslicer.GetOutputExtent().GetY1() + 0.5 * s); + + // TODO - Angle!! + + return layer.release(); + } + else + { + return NULL; + } + } + }; + + + DicomVolumeImageReslicer::DicomVolumeImageReslicer(const boost::shared_ptr<DicomVolumeImage>& volume) : + volume_(volume) + { + if (volume.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + + IVolumeSlicer::IExtractedSlice* DicomVolumeImageReslicer::ExtractSlice(const CoordinateSystem3D& cuttingPlane) + { + if (volume_->HasGeometry()) + { + return new Slice(*this, cuttingPlane); + } + else + { + return new IVolumeSlicer::InvalidSlice; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/DicomVolumeImageReslicer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,69 @@ +/** + * 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 "DicomVolumeImage.h" +#include "IVolumeSlicer.h" +#include "VolumeReslicer.h" + +#include <boost/shared_ptr.hpp> + +namespace OrthancStone +{ + /** + This class is able to supply an extract slice for an arbitrary cutting + plane through a volume image + */ + class DicomVolumeImageReslicer : public IVolumeSlicer + { + private: + class Slice; + + boost::shared_ptr<DicomVolumeImage> volume_; + VolumeReslicer reslicer_; + + public: + DicomVolumeImageReslicer(const boost::shared_ptr<DicomVolumeImage>& volume); + + ImageInterpolation GetInterpolation() const + { + return reslicer_.GetInterpolation(); + } + + void SetInterpolation(ImageInterpolation interpolation) + { + reslicer_.SetInterpolation(interpolation); + } + + bool IsFastMode() const + { + return reslicer_.IsFastMode(); + } + + void SetFastMode(bool fast) + { + reslicer_.EnableFastMode(fast); + } + + virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane); + }; +}
--- a/Framework/Volumes/ISlicedVolume.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +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 "../Messages/IObservable.h" -#include "../Toolbox/Slice.h" - -namespace Deprecated -{ - class ISlicedVolume : public OrthancStone::IObservable - { - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, ISlicedVolume); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, ISlicedVolume); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, ISlicedVolume); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeReadyMessage, ISlicedVolume); - - - class SliceContentChangedMessage : public OrthancStone::OriginMessage<ISlicedVolume> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - size_t sliceIndex_; - const Slice& slice_; - - public: - SliceContentChangedMessage(ISlicedVolume& origin, - size_t sliceIndex, - const Slice& slice) : - OriginMessage(origin), - sliceIndex_(sliceIndex), - slice_(slice) - { - } - - size_t GetSliceIndex() const - { - return sliceIndex_; - } - - const Slice& GetSlice() const - { - return slice_; - } - }; - - - ISlicedVolume(OrthancStone::MessageBroker& broker) : - IObservable(broker) - { - } - - virtual size_t GetSliceCount() const = 0; - - virtual const Slice& GetSlice(size_t slice) const = 0; - }; -}
--- a/Framework/Volumes/IVolumeLoader.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +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 "../Messages/IObservable.h" - -namespace Deprecated -{ - class IVolumeLoader : public OrthancStone::IObservable - { - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeLoader); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryErrorMessage, IVolumeLoader); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, IVolumeLoader); - - IVolumeLoader(OrthancStone::MessageBroker& broker) : - IObservable(broker) - { - } - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/IVolumeSlicer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,38 @@ +/** + * 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 "IVolumeSlicer.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + uint64_t IVolumeSlicer::InvalidSlice::GetRevision() + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + ISceneLayer* IVolumeSlicer::InvalidSlice::CreateSceneLayer(const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/IVolumeSlicer.cpp~ Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,113 @@ +/** + * 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 + +namespace OrthancStone +{ + /** + This interface is implemented by objects representing 3D volume data and + that are able to return an object that: + - represent a slice of their data + - are able to create the corresponding slice visual representation. + */ + class IVolumeSlicer : public boost::noncopyable + { + public: + /** + This interface is implemented by objects representing a slice of + volume data and that are able to create a 2D layer to display a this + slice. + + The CreateSceneLayer factory method is called with an optional + configurator that possibly impacts the ISceneLayer subclass that is + created (for instance, if a LUT must be applied on the texture when + displaying it) + */ + class IExtractedSlice : public boost::noncopyable + { + public: + virtual ~IExtractedSlice() + { + } + + /** + Invalid slices are created when the data is not ready yet or if the + cut is outside of the available geometry. + */ + virtual bool IsValid() = 0; + + /** + This retrieves the *revision* that gets incremented every time the + underlying object undergoes a mutable operation (that it, changes its + state). + This **must** be a cheap call. + */ + virtual uint64_t GetRevision() = 0; + + /** Creates the slice visual representation */ + virtual ISceneLayer* CreateSceneLayer( + const ILayerStyleConfigurator* configurator, // possibly absent + const CoordinateSystem3D& cuttingPlane) = 0; + }; + + /** + See IExtractedSlice.IsValid() + */ + class InvalidSlice : public IExtractedSlice + { + public: + virtual bool IsValid() + { + return false; + } + + virtual uint64_t GetRevision() + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + }; + + + virtual ~IVolumeSlicer() + { + } + + /** + This method is implemented by the objects representing volumetric data + and must returns an IExtractedSlice subclass that contains all the data + needed to, later on, create its visual representation through + CreateSceneLayer. + Subclasses a.o.: + - InvalidSlice, + - DicomVolumeImageMPRSlicer::Slice, + - DicomVolumeImageReslicer::Slice + - DicomStructureSetLoader::Slice + */ + virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/IVolumeSlicer.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,110 @@ +/** + * 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 "../Scene2D/ILayerStyleConfigurator.h" +#include "../Toolbox/CoordinateSystem3D.h" + +namespace OrthancStone +{ + /** + This interface is implemented by objects representing 3D volume data and + that are able to return an object that: + - represent a slice of their data + - are able to create the corresponding slice visual representation. + */ + class IVolumeSlicer : public boost::noncopyable + { + public: + /** + This interface is implemented by objects representing a slice of + volume data and that are able to create a 2D layer to display a this + slice. + + The CreateSceneLayer factory method is called with an optional + configurator that possibly impacts the ISceneLayer subclass that is + created (for instance, if a LUT must be applied on the texture when + displaying it) + */ + class IExtractedSlice : public boost::noncopyable + { + public: + virtual ~IExtractedSlice() + { + } + + /** + Invalid slices are created when the data is not ready yet or if the + cut is outside of the available geometry. + */ + virtual bool IsValid() = 0; + + /** + This retrieves the *revision* that gets incremented every time the + underlying object undergoes a mutable operation (that it, changes its + state). + This **must** be a cheap call. + */ + virtual uint64_t GetRevision() = 0; + + /** Creates the slice visual representation */ + virtual ISceneLayer* CreateSceneLayer( + const ILayerStyleConfigurator* configurator, // possibly absent + const CoordinateSystem3D& cuttingPlane) = 0; + }; + + /** + See IExtractedSlice.IsValid() + */ + class InvalidSlice : public IExtractedSlice + { + public: + virtual bool IsValid() + { + return false; + } + + virtual uint64_t GetRevision(); + + virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane); + }; + + + virtual ~IVolumeSlicer() + { + } + + /** + This method is implemented by the objects representing volumetric data + and must returns an IExtractedSlice subclass that contains all the data + needed to, later on, create its visual representation through + CreateSceneLayer. + Subclasses a.o.: + - InvalidSlice, + - DicomVolumeImageMPRSlicer::Slice, + - DicomVolumeImageReslicer::Slice + - DicomStructureSetLoader::Slice + */ + virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/IVolumeSlicer.h~ Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,113 @@ +/** + * 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 + +namespace OrthancStone +{ + /** + This interface is implemented by objects representing 3D volume data and + that are able to return an object that: + - represent a slice of their data + - are able to create the corresponding slice visual representation. + */ + class IVolumeSlicer : public boost::noncopyable + { + public: + /** + This interface is implemented by objects representing a slice of + volume data and that are able to create a 2D layer to display a this + slice. + + The CreateSceneLayer factory method is called with an optional + configurator that possibly impacts the ISceneLayer subclass that is + created (for instance, if a LUT must be applied on the texture when + displaying it) + */ + class IExtractedSlice : public boost::noncopyable + { + public: + virtual ~IExtractedSlice() + { + } + + /** + Invalid slices are created when the data is not ready yet or if the + cut is outside of the available geometry. + */ + virtual bool IsValid() = 0; + + /** + This retrieves the *revision* that gets incremented every time the + underlying object undergoes a mutable operation (that it, changes its + state). + This **must** be a cheap call. + */ + virtual uint64_t GetRevision() = 0; + + /** Creates the slice visual representation */ + virtual ISceneLayer* CreateSceneLayer( + const ILayerStyleConfigurator* configurator, // possibly absent + const CoordinateSystem3D& cuttingPlane) = 0; + }; + + /** + See IExtractedSlice.IsValid() + */ + class InvalidSlice : public IExtractedSlice + { + public: + virtual bool IsValid() + { + return false; + } + + virtual uint64_t GetRevision() + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + }; + + + virtual ~IVolumeSlicer() + { + } + + /** + This method is implemented by the objects representing volumetric data + and must returns an IExtractedSlice subclass that contains all the data + needed to, later on, create its visual representation through + CreateSceneLayer. + Subclasses a.o.: + - InvalidSlice, + - DicomVolumeImageMPRSlicer::Slice, + - DicomVolumeImageReslicer::Slice + - DicomStructureSetLoader::Slice + */ + virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0; + }; +}
--- a/Framework/Volumes/ImageBuffer3D.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Volumes/ImageBuffer3D.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -21,8 +21,6 @@ #include "ImageBuffer3D.h" -#include "../Toolbox/GeometryToolbox.h" - #include <Core/Images/ImageProcessing.h> #include <Core/Logging.h> #include <Core/OrthancException.h> @@ -118,8 +116,6 @@ computeRange_(computeRange), hasRange_(false) { - geometry_.SetSize(width, height, depth); - LOG(INFO) << "Created a 3D image of size " << width << "x" << height << "x" << depth << " in " << Orthanc::EnumerationToString(format) << " (" << (GetEstimatedMemorySize() / (1024ll * 1024ll)) << "MB)"; @@ -132,63 +128,6 @@ } - - - ParallelSlices* ImageBuffer3D::GetGeometry(VolumeProjection projection) const - { - const Vector dimensions = geometry_.GetVoxelDimensions(VolumeProjection_Axial); - const CoordinateSystem3D& axial = geometry_.GetAxialGeometry(); - - std::auto_ptr<ParallelSlices> result(new ParallelSlices); - - switch (projection) - { - case VolumeProjection_Axial: - for (unsigned int z = 0; z < depth_; z++) - { - Vector origin = axial.GetOrigin(); - origin += static_cast<double>(z) * dimensions[2] * axial.GetNormal(); - - result->AddSlice(origin, - axial.GetAxisX(), - axial.GetAxisY()); - } - break; - - case VolumeProjection_Coronal: - for (unsigned int y = 0; y < height_; y++) - { - Vector origin = axial.GetOrigin(); - origin += static_cast<double>(y) * dimensions[1] * axial.GetAxisY(); - origin += static_cast<double>(depth_ - 1) * dimensions[2] * axial.GetNormal(); - - result->AddSlice(origin, - axial.GetAxisX(), - -axial.GetNormal()); - } - break; - - case VolumeProjection_Sagittal: - for (unsigned int x = 0; x < width_; x++) - { - Vector origin = axial.GetOrigin(); - origin += static_cast<double>(x) * dimensions[0] * axial.GetAxisX(); - origin += static_cast<double>(depth_ - 1) * dimensions[2] * axial.GetNormal(); - - result->AddSlice(origin, - axial.GetAxisY(), - -axial.GetNormal()); - } - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - return result.release(); - } - - uint64_t ImageBuffer3D::GetEstimatedMemorySize() const { return image_.GetPitch() * image_.GetHeight() * Orthanc::GetBytesPerPixel(format_); @@ -258,35 +197,6 @@ } - bool ImageBuffer3D::FitWindowingToRange(Deprecated::RenderStyle& style, - const Deprecated::DicomFrameConverter& converter) const - { - if (hasRange_) - { - style.windowing_ = ImageWindowing_Custom; - - // casting the narrower type to wider before calling the + operator - // will prevent overflowing (this is why the cast to double is only - // done on the first operand) - style.customWindowCenter_ = static_cast<float>( - converter.Apply((static_cast<double>(minValue_) + maxValue_) / 2.0)); - - style.customWindowWidth_ = static_cast<float>( - converter.Apply(static_cast<double>(maxValue_) - minValue_)); - - if (style.customWindowWidth_ > 1) - { - return true; - } - } - - style.windowing_ = ImageWindowing_Custom; - style.customWindowCenter_ = 128.0; - style.customWindowWidth_ = 256.0; - return false; - } - - ImageBuffer3D::SliceReader::SliceReader(const ImageBuffer3D& that, VolumeProjection projection, unsigned int slice)
--- a/Framework/Volumes/ImageBuffer3D.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Volumes/ImageBuffer3D.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,10 +22,7 @@ #pragma once #include "../StoneEnumerations.h" -#include "../Layers/RenderStyle.h" -#include "../Toolbox/VolumeImageGeometry.h" -#include "../Toolbox/DicomFrameConverter.h" -#include "../Toolbox/ParallelSlices.h" +#include "../Toolbox/LinearAlgebra.h" #include <Core/Images/Image.h> @@ -34,7 +31,6 @@ class ImageBuffer3D : public boost::noncopyable { private: - VolumeImageGeometry geometry_; // TODO => Move this out of this class Orthanc::Image image_; Orthanc::PixelFormat format_; unsigned int width_; @@ -78,16 +74,6 @@ void Clear(); - VolumeImageGeometry& GetGeometry() - { - return geometry_; - } - - const VolumeImageGeometry& GetGeometry() const - { - return geometry_; - } - const Orthanc::ImageAccessor& GetInternalImage() const { return image_; @@ -113,17 +99,16 @@ return format_; } - // TODO - Remove - ParallelSlices* GetGeometry(VolumeProjection projection) const; - + unsigned int GetBytesPerPixel() const + { + return Orthanc::GetBytesPerPixel(format_); + } + uint64_t GetEstimatedMemorySize() const; bool GetRange(float& minValue, float& maxValue) const; - bool FitWindowingToRange(Deprecated::RenderStyle& style, - const Deprecated::DicomFrameConverter& converter) const; - uint8_t GetVoxelGrayscale8Unchecked(unsigned int x, unsigned int y, unsigned int z) const
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/OrientedVolumeBoundingBox.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,268 @@ +/** + * 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 "OrientedVolumeBoundingBox.h" + +#include "../Toolbox/GeometryToolbox.h" +#include "ImageBuffer3D.h" + +#include <Core/OrthancException.h> + +#include <cassert> + +namespace OrthancStone +{ + OrientedVolumeBoundingBox::OrientedVolumeBoundingBox(const VolumeImageGeometry& geometry) + { + unsigned int n = geometry.GetDepth(); + if (n < 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + Vector dim = geometry.GetVoxelDimensions(VolumeProjection_Axial); + + u_ = geometry.GetAxialGeometry().GetAxisX(); + v_ = geometry.GetAxialGeometry().GetAxisY(); + w_ = geometry.GetAxialGeometry().GetNormal(); + + hu_ = static_cast<double>(geometry.GetWidth() * dim[0] / 2.0); + hv_ = static_cast<double>(geometry.GetHeight() * dim[1] / 2.0); + hw_ = static_cast<double>(geometry.GetDepth() * dim[2] / 2.0); + + c_ = (geometry.GetAxialGeometry().GetOrigin() + + (hu_ - dim[0] / 2.0) * u_ + + (hv_ - dim[1] / 2.0) * v_ + + (hw_ - dim[2] / 2.0) * w_); + } + + + bool OrientedVolumeBoundingBox::HasIntersectionWithPlane(std::vector<Vector>& points, + const Vector& normal, + double d) const + { + assert(normal.size() == 3); + + double r = (hu_ * fabs(boost::numeric::ublas::inner_prod(normal, u_)) + + hv_ * fabs(boost::numeric::ublas::inner_prod(normal, v_)) + + hw_ * fabs(boost::numeric::ublas::inner_prod(normal, w_))); + + double s = boost::numeric::ublas::inner_prod(normal, c_) + d; + + if (fabs(s) >= r) + { + // No intersection, or intersection is reduced to a single point + return false; + } + else + { + Vector p; + + // Loop over all the 12 edges (segments) of the oriented + // bounding box, and check whether they intersect the plane + + // X-aligned edges + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ - u_ * hu_ - v_ * hv_ - w_ * hw_, + c_ + u_ * hu_ - v_ * hv_ - w_ * hw_)) + { + points.push_back(p); + } + + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ - u_ * hu_ + v_ * hv_ - w_ * hw_, + c_ + u_ * hu_ + v_ * hv_ - w_ * hw_)) + { + points.push_back(p); + } + + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ - u_ * hu_ - v_ * hv_ + w_ * hw_, + c_ + u_ * hu_ - v_ * hv_ + w_ * hw_)) + { + points.push_back(p); + } + + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ - u_ * hu_ + v_ * hv_ + w_ * hw_, + c_ + u_ * hu_ + v_ * hv_ + w_ * hw_)) + { + points.push_back(p); + } + + // Y-aligned edges + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ - u_ * hu_ - v_ * hv_ - w_ * hw_, + c_ - u_ * hu_ + v_ * hv_ - w_ * hw_)) + { + points.push_back(p); + } + + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ + u_ * hu_ - v_ * hv_ - w_ * hw_, + c_ + u_ * hu_ + v_ * hv_ - w_ * hw_)) + { + points.push_back(p); + } + + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ - u_ * hu_ - v_ * hv_ + w_ * hw_, + c_ - u_ * hu_ + v_ * hv_ + w_ * hw_)) + { + points.push_back(p); + } + + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ + u_ * hu_ - v_ * hv_ + w_ * hw_, + c_ + u_ * hu_ + v_ * hv_ + w_ * hw_)) + { + points.push_back(p); + } + + // Z-aligned edges + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ - u_ * hu_ - v_ * hv_ - w_ * hw_, + c_ - u_ * hu_ - v_ * hv_ + w_ * hw_)) + { + points.push_back(p); + } + + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ + u_ * hu_ - v_ * hv_ - w_ * hw_, + c_ + u_ * hu_ - v_ * hv_ + w_ * hw_)) + { + points.push_back(p); + } + + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ - u_ * hu_ + v_ * hv_ - w_ * hw_, + c_ - u_ * hu_ + v_ * hv_ + w_ * hw_)) + { + points.push_back(p); + } + + if (GeometryToolbox::IntersectPlaneAndSegment + (p, normal, d, + c_ + u_ * hu_ + v_ * hv_ - w_ * hw_, + c_ + u_ * hu_ + v_ * hv_ + w_ * hw_)) + { + points.push_back(p); + } + + return true; + } + } + + + bool OrientedVolumeBoundingBox::HasIntersection(std::vector<Vector>& points, + const CoordinateSystem3D& plane) const + { + // From the vector equation of a 3D plane (specified by origin + // and normal), to the general equation of a 3D plane (which + // looses information about the origin of the coordinate system) + const Vector& normal = plane.GetNormal(); + const Vector& origin = plane.GetOrigin(); + double d = -(normal[0] * origin[0] + normal[1] * origin[1] + normal[2] * origin[2]); + + return HasIntersectionWithPlane(points, normal, d); + } + + + bool OrientedVolumeBoundingBox::Contains(const Vector& p) const + { + assert(p.size() == 3); + + const Vector q = p - c_; + + return (fabs(boost::numeric::ublas::inner_prod(q, u_)) <= hu_ && + fabs(boost::numeric::ublas::inner_prod(q, v_)) <= hv_ && + fabs(boost::numeric::ublas::inner_prod(q, w_)) <= hw_); + } + + + void OrientedVolumeBoundingBox::FromInternalCoordinates(Vector& target, + double x, + double y, + double z) const + { + target = (c_ + + u_ * 2.0 * hu_ * (x - 0.5) + + v_ * 2.0 * hv_ * (y - 0.5) + + w_ * 2.0 * hw_ * (z - 0.5)); + } + + + void OrientedVolumeBoundingBox::FromInternalCoordinates(Vector& target, + const Vector& source) const + { + assert(source.size() == 3); + FromInternalCoordinates(target, source[0], source[1], source[2]); + } + + + void OrientedVolumeBoundingBox::ToInternalCoordinates(Vector& target, + const Vector& source) const + { + assert(source.size() == 3); + const Vector q = source - c_; + + double x = boost::numeric::ublas::inner_prod(q, u_) / (2.0 * hu_) + 0.5; + double y = boost::numeric::ublas::inner_prod(q, v_) / (2.0 * hv_) + 0.5; + double z = boost::numeric::ublas::inner_prod(q, w_) / (2.0 * hw_) + 0.5; + + LinearAlgebra::AssignVector(target, x, y, z); + } + + + bool OrientedVolumeBoundingBox::ComputeExtent(Extent2D& extent, + const CoordinateSystem3D& plane) const + { + extent.Reset(); + + std::vector<Vector> points; + if (HasIntersection(points, plane)) + { + for (size_t i = 0; i < points.size(); i++) + { + double x, y; + plane.ProjectPoint(x, y, points[i]); + extent.AddPoint(x, y); + } + + return true; + } + else + { + return false; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/OrientedVolumeBoundingBox.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,74 @@ +/** + * 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/CoordinateSystem3D.h" +#include "../Toolbox/Extent2D.h" +#include "../Toolbox/LinearAlgebra.h" +#include "VolumeImageGeometry.h" + +namespace OrthancStone +{ + class OrientedVolumeBoundingBox : public boost::noncopyable + { + private: + Vector c_; // center + Vector u_; // normalized width vector + Vector v_; // normalized height vector + Vector w_; // normalized depth vector + double hu_; // half width + double hv_; // half height + double hw_; // half depth + + public: + OrientedVolumeBoundingBox(const VolumeImageGeometry& geometry); + + const Vector& GetCenter() const + { + return c_; + } + + bool HasIntersectionWithPlane(std::vector<Vector>& points, + const Vector& normal, + double d) const; + + bool HasIntersection(std::vector<Vector>& points, + const CoordinateSystem3D& plane) const; + + bool Contains(const Vector& p) const; + + void FromInternalCoordinates(Vector& target, + double x, + double y, + double z) const; + + void FromInternalCoordinates(Vector& target, + const Vector& source) const; + + void ToInternalCoordinates(Vector& target, + const Vector& source) const; + + bool ComputeExtent(Extent2D& extent, + const CoordinateSystem3D& plane) const; + }; +} +
--- a/Framework/Volumes/StructureSetLoader.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +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 "StructureSetLoader.h" - -#include "../Toolbox/MessagingToolbox.h" - -#include <Core/OrthancException.h> - -namespace Deprecated -{ - StructureSetLoader::StructureSetLoader(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc) : - IVolumeLoader(broker), - IObserver(broker), - orthanc_(orthanc) - { - } - - - void StructureSetLoader::OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message) - { - OrthancPlugins::FullOrthancDataset dataset(message.GetJson()); - - Orthanc::DicomMap slice; - OrthancStone::MessagingToolbox::ConvertDataset(slice, dataset); - structureSet_->AddReferencedSlice(slice); - - BroadcastMessage(ContentChangedMessage(*this)); - } - - - void StructureSetLoader::OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message) - { - OrthancPlugins::FullOrthancDataset dataset(message.GetJson()); - structureSet_.reset(new OrthancStone::DicomStructureSet(dataset)); - - std::set<std::string> instances; - structureSet_->GetReferencedInstances(instances); - - for (std::set<std::string>::const_iterator it = instances.begin(); - it != instances.end(); ++it) - { - orthanc_.PostBinaryAsyncExpectJson("/tools/lookup", *it, - new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnLookupCompleted)); - } - - BroadcastMessage(GeometryReadyMessage(*this)); - } - - - void StructureSetLoader::OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message) - { - const Json::Value& lookup = message.GetJson(); - - if (lookup.type() != Json::arrayValue || - lookup.size() != 1 || - !lookup[0].isMember("Type") || - !lookup[0].isMember("Path") || - lookup[0]["Type"].type() != Json::stringValue || - lookup[0]["ID"].type() != Json::stringValue || - lookup[0]["Type"].asString() != "Instance") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - const std::string& instance = lookup[0]["ID"].asString(); - orthanc_.GetJsonAsync("/instances/" + instance + "/tags", - new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnReferencedSliceLoaded)); - } - - - void StructureSetLoader::ScheduleLoadInstance(const std::string& instance) - { - if (structureSet_.get() != NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - orthanc_.GetJsonAsync("/instances/" + instance + "/tags?ignore-length=3006-0050", - new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnStructureSetLoaded)); - } - } - - - OrthancStone::DicomStructureSet& StructureSetLoader::GetStructureSet() - { - if (structureSet_.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - return *structureSet_; - } - } -}
--- a/Framework/Volumes/StructureSetLoader.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +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 "../Toolbox/DicomStructureSet.h" -#include "../Toolbox/OrthancApiClient.h" -#include "IVolumeLoader.h" - -namespace Deprecated -{ - class StructureSetLoader : - public IVolumeLoader, - public OrthancStone::IObserver - { - private: - OrthancApiClient& orthanc_; - std::auto_ptr<OrthancStone::DicomStructureSet> structureSet_; - - void OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message); - - void OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message); - - void OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message); - - public: - StructureSetLoader(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc); - - void ScheduleLoadInstance(const std::string& instance); - - bool HasStructureSet() const - { - return structureSet_.get() != NULL; - } - - OrthancStone::DicomStructureSet& GetStructureSet(); - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/VolumeImageGeometry.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,326 @@ +/** + * 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 "VolumeImageGeometry.h" + +#include "../Toolbox/GeometryToolbox.h" + +#include <Core/OrthancException.h> + + +namespace OrthancStone +{ + void VolumeImageGeometry::Invalidate() + { + Vector p = (axialGeometry_.GetOrigin() + + static_cast<double>(depth_ - 1) * voxelDimensions_[2] * axialGeometry_.GetNormal()); + + coronalGeometry_ = CoordinateSystem3D(p, + axialGeometry_.GetAxisX(), + -axialGeometry_.GetNormal()); + + sagittalGeometry_ = CoordinateSystem3D(p, + axialGeometry_.GetAxisY(), + axialGeometry_.GetNormal()); + + Vector origin = ( + axialGeometry_.MapSliceToWorldCoordinates(-0.5 * voxelDimensions_[0], + -0.5 * voxelDimensions_[1]) - + 0.5 * voxelDimensions_[2] * axialGeometry_.GetNormal()); + + Vector scaling; + + if (width_ == 0 || + height_ == 0 || + depth_ == 0) + { + LinearAlgebra::AssignVector(scaling, 1, 1, 1); + } + else + { + scaling = ( + axialGeometry_.GetAxisX() * voxelDimensions_[0] * static_cast<double>(width_) + + axialGeometry_.GetAxisY() * voxelDimensions_[1] * static_cast<double>(height_) + + axialGeometry_.GetNormal() * voxelDimensions_[2] * static_cast<double>(depth_)); + } + + transform_ = LinearAlgebra::Product( + GeometryToolbox::CreateTranslationMatrix(origin[0], origin[1], origin[2]), + GeometryToolbox::CreateScalingMatrix(scaling[0], scaling[1], scaling[2])); + + LinearAlgebra::InvertMatrix(transformInverse_, transform_); + } + + + VolumeImageGeometry::VolumeImageGeometry() : + width_(0), + height_(0), + depth_(0) + { + LinearAlgebra::AssignVector(voxelDimensions_, 1, 1, 1); + Invalidate(); + } + + + void VolumeImageGeometry::SetSize(unsigned int width, + unsigned int height, + unsigned int depth) + { + width_ = width; + height_ = height; + depth_ = depth; + Invalidate(); + } + + + void VolumeImageGeometry::SetAxialGeometry(const CoordinateSystem3D& geometry) + { + axialGeometry_ = geometry; + Invalidate(); + } + + + void VolumeImageGeometry::SetVoxelDimensions(double x, + double y, + double z) + { + if (x <= 0 || + y <= 0 || + z <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + LinearAlgebra::AssignVector(voxelDimensions_, x, y, z); + Invalidate(); + } + } + + + const CoordinateSystem3D& VolumeImageGeometry::GetProjectionGeometry(VolumeProjection projection) const + { + switch (projection) + { + case VolumeProjection_Axial: + return axialGeometry_; + + case VolumeProjection_Coronal: + return coronalGeometry_; + + case VolumeProjection_Sagittal: + return sagittalGeometry_; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + Vector VolumeImageGeometry::GetVoxelDimensions(VolumeProjection projection) const + { + switch (projection) + { + case VolumeProjection_Axial: + return voxelDimensions_; + + case VolumeProjection_Coronal: + return LinearAlgebra::CreateVector(voxelDimensions_[0], voxelDimensions_[2], voxelDimensions_[1]); + + case VolumeProjection_Sagittal: + return LinearAlgebra::CreateVector(voxelDimensions_[1], voxelDimensions_[2], voxelDimensions_[0]); + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + unsigned int VolumeImageGeometry::GetProjectionWidth(VolumeProjection projection) const + { + switch (projection) + { + case VolumeProjection_Axial: + return width_; + + case VolumeProjection_Coronal: + return width_; + + case VolumeProjection_Sagittal: + return height_; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + unsigned int VolumeImageGeometry::GetProjectionHeight(VolumeProjection projection) const + { + switch (projection) + { + case VolumeProjection_Axial: + return height_; + + case VolumeProjection_Coronal: + return depth_; + + case VolumeProjection_Sagittal: + return depth_; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + unsigned int VolumeImageGeometry::GetProjectionDepth(VolumeProjection projection) const + { + switch (projection) + { + case VolumeProjection_Axial: + return depth_; + + case VolumeProjection_Coronal: + return height_; + + case VolumeProjection_Sagittal: + return width_; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + Vector VolumeImageGeometry::GetCoordinates(float x, + float y, + float z) const + { + Vector p = LinearAlgebra::Product(transform_, LinearAlgebra::CreateVector(x, y, z, 1)); + + assert(LinearAlgebra::IsNear(p[3], 1)); // Affine transform, no perspective effect + + // Back to non-homogeneous coordinates + return LinearAlgebra::CreateVector(p[0], p[1], p[2]); + } + + + bool VolumeImageGeometry::DetectProjection(VolumeProjection& projection, + const Vector& planeNormal) const + { + if (GeometryToolbox::IsParallel(planeNormal, axialGeometry_.GetNormal())) + { + projection = VolumeProjection_Axial; + return true; + } + else if (GeometryToolbox::IsParallel(planeNormal, coronalGeometry_.GetNormal())) + { + projection = VolumeProjection_Coronal; + return true; + } + else if (GeometryToolbox::IsParallel(planeNormal, sagittalGeometry_.GetNormal())) + { + projection = VolumeProjection_Sagittal; + return true; + } + else + { + return false; + } + } + + + bool VolumeImageGeometry::DetectSlice(VolumeProjection& projection, + unsigned int& slice, + const CoordinateSystem3D& plane) const + { + if (!DetectProjection(projection, plane.GetNormal())) + { + return false; + } + + // Transforms the coordinates of the origin of the plane, into the + // coordinates of the axial geometry + const Vector& origin = plane.GetOrigin(); + Vector p = LinearAlgebra::Product( + transformInverse_, + LinearAlgebra::CreateVector(origin[0], origin[1], origin[2], 1)); + + assert(LinearAlgebra::IsNear(p[3], 1)); + + double z; + + switch (projection) + { + case VolumeProjection_Axial: + z = p[2]; + break; + + case VolumeProjection_Coronal: + z = p[1]; + break; + + case VolumeProjection_Sagittal: + z = p[0]; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + const unsigned int projectionDepth = GetProjectionDepth(projection); + + z *= static_cast<double>(projectionDepth); + if (z < 0) + { + return false; + } + + unsigned int d = static_cast<unsigned int>(std::floor(z)); + if (d >= projectionDepth) + { + return false; + } + else + { + slice = d; + return true; + } + } + + + CoordinateSystem3D VolumeImageGeometry::GetProjectionSlice(VolumeProjection projection, + unsigned int z) const + { + if (z >= GetProjectionDepth(projection)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + Vector dim = GetVoxelDimensions(projection); + CoordinateSystem3D plane = GetProjectionGeometry(projection); + + plane.SetOrigin(plane.GetOrigin() + static_cast<double>(z) * plane.GetNormal() * dim[2]); + + return plane; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/VolumeImageGeometry.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,134 @@ +/** + * 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 "../StoneEnumerations.h" +#include "../Toolbox/CoordinateSystem3D.h" + +namespace OrthancStone +{ + class VolumeImageGeometry + { + private: + unsigned int width_; + unsigned int height_; + unsigned int depth_; + CoordinateSystem3D axialGeometry_; + CoordinateSystem3D coronalGeometry_; + CoordinateSystem3D sagittalGeometry_; + Vector voxelDimensions_; + Matrix transform_; + Matrix transformInverse_; + + void Invalidate(); + + public: + VolumeImageGeometry(); + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetDepth() const + { + return depth_; + } + + const CoordinateSystem3D& GetAxialGeometry() const + { + return axialGeometry_; + } + + const CoordinateSystem3D& GetCoronalGeometry() const + { + return coronalGeometry_; + } + + const CoordinateSystem3D& GetSagittalGeometry() const + { + return sagittalGeometry_; + } + + const CoordinateSystem3D& GetProjectionGeometry(VolumeProjection projection) const; + + const Matrix& GetTransform() const + { + return transform_; + } + + const Matrix& GetTransformInverse() const + { + return transformInverse_; + } + + void SetSize(unsigned int width, + unsigned int height, + unsigned int depth); + + // Set the geometry of the first axial slice (i.e. the one whose + // depth == 0) + void SetAxialGeometry(const CoordinateSystem3D& geometry); + + void SetVoxelDimensions(double x, + double y, + double z); + + Vector GetVoxelDimensions(VolumeProjection projection) const; + + unsigned int GetProjectionWidth(VolumeProjection projection) const; + + unsigned int GetProjectionHeight(VolumeProjection projection) const; + + unsigned int GetProjectionDepth(VolumeProjection projection) const; + + // Get the 3D position of a point in the volume, where x, y and z + // lie in the [0;1] range + Vector GetCoordinates(float x, + float y, + float z) const; + + bool DetectProjection(VolumeProjection& projection, + const Vector& planeNormal) const; + + /** + Being given a cutting plane, this method will determine if it is an + axial, sagittal or coronal cut and returns + the slice number corresponding to this cut. + + If the cutting plane is not parallel to the tree x = 0, y = 0 or z = 0 + planes, it is considered as arbitrary and the method returns false. + Otherwise, it returns true. + */ + bool DetectSlice(VolumeProjection& projection, + unsigned int& slice, + const CoordinateSystem3D& plane) const; + + CoordinateSystem3D GetProjectionSlice(VolumeProjection projection, + unsigned int z) const; + }; +}
--- a/Framework/Volumes/VolumeReslicer.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Volumes/VolumeReslicer.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -230,7 +230,7 @@ FastRowIterator(const Orthanc::ImageAccessor& slice, const Extent2D& extent, const CoordinateSystem3D& plane, - const OrientedBoundingBox& box, + const OrientedVolumeBoundingBox& box, unsigned int y) { const double width = static_cast<double>(slice.GetWidth()); @@ -285,7 +285,7 @@ const Orthanc::ImageAccessor& slice_; const Extent2D& extent_; const CoordinateSystem3D& plane_; - const OrientedBoundingBox& box_; + const OrientedVolumeBoundingBox& box_; unsigned int x_; unsigned int y_; @@ -293,7 +293,7 @@ SlowRowIterator(const Orthanc::ImageAccessor& slice, const Extent2D& extent, const CoordinateSystem3D& plane, - const OrientedBoundingBox& box, + const OrientedVolumeBoundingBox& box, unsigned int y) : slice_(slice), extent_(extent), @@ -342,7 +342,7 @@ const Extent2D& extent, const ImageBuffer3D& source, const CoordinateSystem3D& plane, - const OrientedBoundingBox& box, + const OrientedVolumeBoundingBox& box, float scaling, float offset) { @@ -386,7 +386,7 @@ const Extent2D& extent, const ImageBuffer3D& source, const CoordinateSystem3D& plane, - const OrientedBoundingBox& box, + const OrientedVolumeBoundingBox& box, ImageInterpolation interpolation, bool hasLinearFunction, float scaling, @@ -452,7 +452,7 @@ const Extent2D& extent, const ImageBuffer3D& source, const CoordinateSystem3D& plane, - const OrientedBoundingBox& box, + const OrientedVolumeBoundingBox& box, ImageInterpolation interpolation, bool hasLinearFunction, float scaling, @@ -501,7 +501,7 @@ void VolumeReslicer::CheckIterators(const ImageBuffer3D& source, const CoordinateSystem3D& plane, - const OrientedBoundingBox& box) const + const OrientedVolumeBoundingBox& box) const { for (unsigned int y = 0; y < slice_->GetHeight(); y++) { @@ -745,12 +745,13 @@ void VolumeReslicer::Apply(const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, const CoordinateSystem3D& plane) { // Choose the default voxel size as the finest voxel dimension // of the source volumetric image const OrthancStone::Vector dim = - source.GetGeometry().GetVoxelDimensions(OrthancStone::VolumeProjection_Axial); + geometry.GetVoxelDimensions(OrthancStone::VolumeProjection_Axial); double voxelSize = dim[0]; if (dim[1] < voxelSize) @@ -768,22 +769,24 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - Apply(source, plane, voxelSize); + Apply(source, geometry, plane, voxelSize); } void VolumeReslicer::Apply(const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, const CoordinateSystem3D& plane, double voxelSize) { Reset(); + pixelSpacing_ = voxelSize; // Firstly, compute the intersection of the source volumetric // image with the reslicing plane. This leads to a polygon with 3 // to 6 vertices. We compute the extent of the intersection // polygon, with respect to the coordinate system of the reslicing // plane. - OrientedBoundingBox box(source); + OrientedVolumeBoundingBox box(geometry); if (!box.ComputeExtent(extent_, plane)) { @@ -815,4 +818,17 @@ success_ = true; } + + + double VolumeReslicer::GetPixelSpacing() const + { + if (success_) + { + return pixelSpacing_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } }
--- a/Framework/Volumes/VolumeReslicer.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Framework/Volumes/VolumeReslicer.h Mon Jun 24 14:35:00 2019 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Toolbox/Extent2D.h" -#include "../Toolbox/OrientedBoundingBox.h" +#include "OrientedVolumeBoundingBox.h" #include "ImageBuffer3D.h" namespace OrthancStone @@ -43,10 +43,11 @@ bool success_; Extent2D extent_; std::auto_ptr<Orthanc::Image> slice_; + double pixelSpacing_; void CheckIterators(const ImageBuffer3D& source, const CoordinateSystem3D& plane, - const OrientedBoundingBox& box) const; + const OrientedVolumeBoundingBox& box) const; void Reset(); @@ -111,10 +112,14 @@ Orthanc::ImageAccessor* ReleaseOutputSlice(); void Apply(const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, const CoordinateSystem3D& plane); void Apply(const ImageBuffer3D& source, + const VolumeImageGeometry& geometry, const CoordinateSystem3D& plane, double voxelSize); + + double GetPixelSpacing() const; }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/VolumeSceneLayerSource.cpp Mon Jun 24 14:35: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-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 "VolumeSceneLayerSource.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + static bool IsSameCuttingPlane(const CoordinateSystem3D& a, + const CoordinateSystem3D& b) + { + // TODO - What if the normal is reversed? + double distance; + return (CoordinateSystem3D::ComputeDistance(distance, a, b) && + LinearAlgebra::IsCloseToZero(distance)); + } + + + void VolumeSceneLayerSource::ClearLayer() + { + scene_.DeleteLayer(layerDepth_); + lastPlane_.reset(NULL); + } + + + VolumeSceneLayerSource::VolumeSceneLayerSource(Scene2D& scene, + int layerDepth, + const boost::shared_ptr<IVolumeSlicer>& slicer) : + scene_(scene), + layerDepth_(layerDepth), + slicer_(slicer) + { + if (slicer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + + void VolumeSceneLayerSource::RemoveConfigurator() + { + configurator_.reset(); + lastPlane_.reset(); + } + + + void VolumeSceneLayerSource::SetConfigurator(ILayerStyleConfigurator* configurator) // Takes ownership + { + if (configurator == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + configurator_.reset(configurator); + + // Invalidate the layer + lastPlane_.reset(NULL); + } + + + ILayerStyleConfigurator& VolumeSceneLayerSource::GetConfigurator() const + { + if (configurator_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + return *configurator_; + } + + + void VolumeSceneLayerSource::Update(const CoordinateSystem3D& plane) + { + assert(slicer_.get() != NULL); + std::auto_ptr<IVolumeSlicer::IExtractedSlice> slice(slicer_->ExtractSlice(plane)); + + if (slice.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (!slice->IsValid()) + { + // The slicer cannot handle this cutting plane: Clear the layer + ClearLayer(); + } + else if (lastPlane_.get() != NULL && + IsSameCuttingPlane(*lastPlane_, plane) && + lastRevision_ == slice->GetRevision()) + { + // The content of the slice has not changed: Don't update the + // layer content, but possibly update its style + + if (configurator_.get() != NULL && + configurator_->GetRevision() != lastConfiguratorRevision_ && + scene_.HasLayer(layerDepth_)) + { + configurator_->ApplyStyle(scene_.GetLayer(layerDepth_)); + } + } + else + { + // Content has changed: An update is needed + lastPlane_.reset(new CoordinateSystem3D(plane)); + lastRevision_ = slice->GetRevision(); + + std::auto_ptr<ISceneLayer> layer(slice->CreateSceneLayer(configurator_.get(), plane)); + if (layer.get() == NULL) + { + ClearLayer(); + } + else + { + if (configurator_.get() != NULL) + { + lastConfiguratorRevision_ = configurator_->GetRevision(); + configurator_->ApplyStyle(*layer); + } + + scene_.SetLayer(layerDepth_, layer.release()); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/VolumeSceneLayerSource.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,74 @@ +/** + * 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 "../Scene2D/Scene2D.h" +#include "IVolumeSlicer.h" + +#include <boost/shared_ptr.hpp> + +namespace OrthancStone +{ + /** + This class applies one "volume slicer" to a "3D volume", in order + to create one "2D scene layer" that will be set onto the "2D + scene". The style of the layer can be fine-tuned using a "layer + style configurator". The class only changes the layer if the + cutting plane has been modified since the last call to "Update()". + **/ + class VolumeSceneLayerSource : public boost::noncopyable + { + private: + Scene2D& scene_; + int layerDepth_; + boost::shared_ptr<IVolumeSlicer> slicer_; + std::auto_ptr<ILayerStyleConfigurator> configurator_; + std::auto_ptr<CoordinateSystem3D> lastPlane_; + uint64_t lastRevision_; + uint64_t lastConfiguratorRevision_; + + void ClearLayer(); + + public: + VolumeSceneLayerSource(Scene2D& scene, + int layerDepth, + const boost::shared_ptr<IVolumeSlicer>& slicer); + + const IVolumeSlicer& GetSlicer() const + { + return *slicer_; + } + + void RemoveConfigurator(); + + void SetConfigurator(ILayerStyleConfigurator* configurator); + + bool HasConfigurator() const + { + return configurator_.get() != NULL; + } + + ILayerStyleConfigurator& GetConfigurator() const; + + void Update(const CoordinateSystem3D& plane); + }; +}
--- a/Framework/Widgets/CairoWidget.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +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 "CairoWidget.h" - -#include <Core/Images/ImageProcessing.h> -#include <Core/OrthancException.h> - -namespace Deprecated -{ - static bool IsAligned(const Orthanc::ImageAccessor& target) - { - // TODO - return true; - } - - CairoWidget::CairoWidget(const std::string& name) : - WidgetBase(name) - { - } - - void CairoWidget::SetSize(unsigned int width, - unsigned int height) - { - surface_.SetSize(width, height, false /* no alpha */); - } - - - bool CairoWidget::Render(Orthanc::ImageAccessor& target) - { - // Don't call the base class here, as - // "ClearBackgroundCairo()" is a faster alternative - - if (IsAligned(target)) - { - OrthancStone::CairoSurface surface(target, false /* no alpha */); - OrthancStone::CairoContext context(surface); - ClearBackgroundCairo(context); - return RenderCairo(context); - } - else - { - OrthancStone::CairoContext context(surface_); - ClearBackgroundCairo(context); - - if (RenderCairo(context)) - { - Orthanc::ImageAccessor surface; - surface_.GetReadOnlyAccessor(surface); - Orthanc::ImageProcessing::Copy(target, surface); - return true; - } - else - { - return false; - } - } - } - - - void CairoWidget::RenderMouseOver(Orthanc::ImageAccessor& target, - int x, - int y) - { - if (IsAligned(target)) - { - OrthancStone::CairoSurface surface(target, false /* no alpha */); - OrthancStone::CairoContext context(surface); - RenderMouseOverCairo(context, x, y); - } - else - { - Orthanc::ImageAccessor accessor; - surface_.GetWriteableAccessor(accessor); - Orthanc::ImageProcessing::Copy(accessor, target); - - OrthancStone::CairoContext context(surface_); - RenderMouseOverCairo(context, x, y); - - Orthanc::ImageProcessing::Copy(target, accessor); - } - } -}
--- a/Framework/Widgets/CairoWidget.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +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 "WidgetBase.h" - -namespace Deprecated -{ - class CairoWidget : public WidgetBase - { - private: - OrthancStone::CairoSurface surface_; - - protected: - virtual bool RenderCairo(OrthancStone::CairoContext& context) = 0; - - virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context, - int x, - int y) = 0; - - public: - CairoWidget(const std::string& name); - - virtual void SetSize(unsigned int width, - unsigned int height); - - virtual bool Render(Orthanc::ImageAccessor& target); - - virtual void RenderMouseOver(Orthanc::ImageAccessor& target, - int x, - int y); - }; -}
--- a/Framework/Widgets/EmptyWidget.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +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 "EmptyWidget.h" - -#include <Core/Images/ImageProcessing.h> -#include <Core/OrthancException.h> - -namespace Deprecated -{ - bool EmptyWidget::Render(Orthanc::ImageAccessor& surface) - { - // Note: This call is slow - Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255); - return true; - } - - - void EmptyWidget::DoAnimation() - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } -}
--- a/Framework/Widgets/EmptyWidget.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +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 "IWidget.h" - -namespace Deprecated -{ - /** - * This is a test widget that simply fills its surface with an - * uniform color. - **/ - class EmptyWidget : public IWidget - { - private: - uint8_t red_; - uint8_t green_; - uint8_t blue_; - - public: - EmptyWidget(uint8_t red, - uint8_t green, - uint8_t blue) : - red_(red), - green_(green), - blue_(blue) - { - } - - virtual void FitContent() - { - } - - virtual void SetParent(IWidget& widget) - { - } - - virtual void SetViewport(WidgetViewport& viewport) - { - } - - virtual void NotifyContentChanged() - { - } - - virtual void SetStatusBar(IStatusBar& statusBar) - { - } - - virtual void SetSize(unsigned int width, - unsigned int height) - { - } - - virtual bool Render(Orthanc::ImageAccessor& surface); - - virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& touches) - { - return NULL; - } - - virtual void RenderMouseOver(Orthanc::ImageAccessor& target, - int x, - int y) - { - } - - virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers) - { - } - - virtual void KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers) - { - } - - virtual bool HasAnimation() const - { - return false; - } - - virtual void DoAnimation(); - - virtual bool HasRenderMouseOver() - { - return false; - } - }; -}
--- a/Framework/Widgets/IWidget.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +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 "../StoneEnumerations.h" -#include "../Viewport/IMouseTracker.h" -#include "../Viewport/IStatusBar.h" - -namespace Deprecated -{ - class WidgetViewport; // Forward declaration - - class IWidget : public boost::noncopyable - { - public: - virtual ~IWidget() - { - } - - virtual void FitContent() = 0; - - virtual void SetParent(IWidget& parent) = 0; - - virtual void SetViewport(WidgetViewport& viewport) = 0; - - virtual void SetStatusBar(IStatusBar& statusBar) = 0; - - virtual void SetSize(unsigned int width, - unsigned int height) = 0; - - virtual bool Render(Orthanc::ImageAccessor& surface) = 0; - - virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& touches) = 0; - - virtual void RenderMouseOver(Orthanc::ImageAccessor& target, - int x, - int y) = 0; - - virtual bool HasRenderMouseOver() = 0; - - virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers) = 0; - - virtual void KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers) = 0; - - virtual bool HasAnimation() const = 0; - - virtual void DoAnimation() = 0; - - // Subclasses can call this method to signal the display of the - // widget must be refreshed - virtual void NotifyContentChanged() = 0; - }; -}
--- a/Framework/Widgets/IWorldSceneInteractor.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +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 "IWorldSceneMouseTracker.h" - -#include "../Toolbox/ViewportGeometry.h" -#include "../StoneEnumerations.h" -#include "../Viewport/IStatusBar.h" - -namespace Deprecated -{ - class WorldSceneWidget; - - class IWorldSceneInteractor : public boost::noncopyable - { - public: - virtual ~IWorldSceneInteractor() - { - } - - virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, - const ViewportGeometry& view, - OrthancStone::MouseButton button, - OrthancStone::KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - IStatusBar* statusBar, - const std::vector<Touch>& touches) = 0; - - virtual void MouseOver(OrthancStone::CairoContext& context, - WorldSceneWidget& widget, - const ViewportGeometry& view, - double x, - double y, - IStatusBar* statusBar) = 0; - - virtual void MouseWheel(WorldSceneWidget& widget, - OrthancStone::MouseWheelDirection direction, - OrthancStone::KeyboardModifiers modifiers, - IStatusBar* statusBar) = 0; - - virtual void KeyPressed(WorldSceneWidget& widget, - OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers, - IStatusBar* statusBar) = 0; - }; -}
--- a/Framework/Widgets/IWorldSceneMouseTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +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 "../Viewport/CairoContext.h" -#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition - -namespace Deprecated -{ - - // this is tracking a mouse in scene coordinates/mm unlike - // the IMouseTracker that is tracking a mouse - // in screen coordinates/pixels. - class IWorldSceneMouseTracker : public boost::noncopyable - { - public: - virtual ~IWorldSceneMouseTracker() - { - } - - virtual bool HasRender() const = 0; - - virtual void Render(OrthancStone::CairoContext& context, - double zoom) = 0; - - virtual void MouseUp() = 0; - - virtual void MouseMove(int displayX, - int displayY, - double sceneX, - double sceneY, - const std::vector<Touch>& displayTouches, - const std::vector<Touch>& sceneTouches) = 0; - }; -}
--- a/Framework/Widgets/LayoutWidget.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,503 +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 "LayoutWidget.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> - -#include <boost/math/special_functions/round.hpp> - -namespace Deprecated -{ - class LayoutWidget::LayoutMouseTracker : public IMouseTracker - { - private: - std::auto_ptr<IMouseTracker> tracker_; - int left_; - int top_; - unsigned int width_; - unsigned int height_; - - public: - LayoutMouseTracker(IMouseTracker* tracker, - int left, - int top, - unsigned int width, - unsigned int height) : - tracker_(tracker), - left_(left), - top_(top), - width_(width), - height_(height) - { - if (tracker == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - virtual void Render(Orthanc::ImageAccessor& surface) - { - Orthanc::ImageAccessor accessor; - surface.GetRegion(accessor, left_, top_, width_, height_); - tracker_->Render(accessor); - } - - virtual void MouseUp() - { - tracker_->MouseUp(); - } - - virtual void MouseMove(int x, - int y, - const std::vector<Touch>& displayTouches) - { - 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); - } - }; - - - class LayoutWidget::ChildWidget : public boost::noncopyable - { - private: - std::auto_ptr<IWidget> widget_; - int left_; - int top_; - unsigned int width_; - unsigned int height_; - - public: - ChildWidget(IWidget* widget) : - widget_(widget) - { - assert(widget != NULL); - SetEmpty(); - } - - void DoAnimation() - { - if (widget_->HasAnimation()) - { - widget_->DoAnimation(); - } - } - - IWidget& GetWidget() const - { - return *widget_; - } - - void SetRectangle(unsigned int left, - unsigned int top, - unsigned int width, - unsigned int height) - { - left_ = left; - top_ = top; - width_ = width; - height_ = height; - - widget_->SetSize(width, height); - } - - void SetEmpty() - { - SetRectangle(0, 0, 0, 0); - } - - bool Contains(int x, - int y) const - { - return (x >= left_ && - y >= top_ && - x < left_ + static_cast<int>(width_) && - y < top_ + static_cast<int>(height_)); - } - - bool Render(Orthanc::ImageAccessor& target) - { - if (width_ == 0 || - height_ == 0) - { - return true; - } - else - { - Orthanc::ImageAccessor accessor; - target.GetRegion(accessor, left_, top_, width_, height_); - return widget_->Render(accessor); - } - } - - IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& touches) - { - if (Contains(x, y)) - { - IMouseTracker* tracker = widget_->CreateMouseTracker(button, - x - left_, - y - top_, - modifiers, - touches); - if (tracker) - { - return new LayoutMouseTracker(tracker, left_, top_, width_, height_); - } - } - - return NULL; - } - - void RenderMouseOver(Orthanc::ImageAccessor& target, - int x, - int y) - { - if (Contains(x, y)) - { - Orthanc::ImageAccessor accessor; - target.GetRegion(accessor, left_, top_, width_, height_); - - widget_->RenderMouseOver(accessor, x - left_, y - top_); - } - } - - void MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers) - { - if (Contains(x, y)) - { - widget_->MouseWheel(direction, x - left_, y - top_, modifiers); - } - } - - bool HasRenderMouseOver() - { - return widget_->HasRenderMouseOver(); - } - }; - - - void LayoutWidget::ComputeChildrenExtents() - { - if (children_.size() == 0) - { - return; - } - - float internal = static_cast<float>(paddingInternal_); - - if (width_ <= paddingLeft_ + paddingRight_ || - height_ <= paddingTop_ + paddingBottom_) - { - for (size_t i = 0; i < children_.size(); i++) - { - children_[i]->SetEmpty(); - } - } - else if (isHorizontal_) - { - 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())); - - for (size_t i = 0; i < children_.size(); i++) - { - float left = static_cast<float>(paddingLeft_) + static_cast<float>(i) * (childWidth + internal); - float right = left + childWidth; - - if (left >= right) - { - children_[i]->SetEmpty(); - } - else - { - children_[i]->SetRectangle(static_cast<unsigned int>(left), - paddingTop_, - boost::math::iround(right - left), - height_ - paddingTop_ - paddingBottom_); - } - } - } - else - { - 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())); - - for (size_t i = 0; i < children_.size(); i++) - { - float top = static_cast<float>(paddingTop_) + static_cast<float>(i) * (childHeight + internal); - float bottom = top + childHeight; - - if (top >= bottom) - { - children_[i]->SetEmpty(); - } - else - { - children_[i]->SetRectangle(paddingTop_, - static_cast<unsigned int>(top), - width_ - paddingLeft_ - paddingRight_, - boost::math::iround(bottom - top)); - } - } - } - - NotifyContentChanged(*this); - } - - - LayoutWidget::LayoutWidget(const std::string& name) : - WidgetBase(name), - isHorizontal_(true), - width_(0), - height_(0), - paddingLeft_(0), - paddingTop_(0), - paddingRight_(0), - paddingBottom_(0), - paddingInternal_(0) - { - } - - - LayoutWidget::~LayoutWidget() - { - for (size_t i = 0; i < children_.size(); i++) - { - delete children_[i]; - } - } - - - void LayoutWidget::FitContent() - { - for (size_t i = 0; i < children_.size(); i++) - { - children_[i]->GetWidget().FitContent(); - } - } - - - void LayoutWidget::NotifyContentChanged(const IWidget& widget) - { - // One of the children has changed - WidgetBase::NotifyContentChanged(); - } - - - void LayoutWidget::SetHorizontal() - { - isHorizontal_ = true; - ComputeChildrenExtents(); - } - - - void LayoutWidget::SetVertical() - { - isHorizontal_ = false; - ComputeChildrenExtents(); - } - - - void LayoutWidget::SetPadding(unsigned int left, - unsigned int top, - unsigned int right, - unsigned int bottom, - unsigned int spacing) - { - paddingLeft_ = left; - paddingTop_ = top; - paddingRight_ = right; - paddingBottom_ = bottom; - paddingInternal_ = spacing; - } - - - void LayoutWidget::SetPadding(unsigned int padding) - { - paddingLeft_ = padding; - paddingTop_ = padding; - paddingRight_ = padding; - paddingBottom_ = padding; - paddingInternal_ = padding; - } - - - IWidget& LayoutWidget::AddWidget(IWidget* widget) // Takes ownership - { - if (widget == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - if (GetStatusBar() != NULL) - { - widget->SetStatusBar(*GetStatusBar()); - } - - children_.push_back(new ChildWidget(widget)); - widget->SetParent(*this); - - ComputeChildrenExtents(); - - if (widget->HasAnimation()) - { - hasAnimation_ = true; - } - - return *widget; - } - - - void LayoutWidget::SetStatusBar(IStatusBar& statusBar) - { - WidgetBase::SetStatusBar(statusBar); - - for (size_t i = 0; i < children_.size(); i++) - { - children_[i]->GetWidget().SetStatusBar(statusBar); - } - } - - - void LayoutWidget::SetSize(unsigned int width, - unsigned int height) - { - width_ = width; - height_ = height; - ComputeChildrenExtents(); - } - - - bool LayoutWidget::Render(Orthanc::ImageAccessor& surface) - { - if (!WidgetBase::Render(surface)) - { - return false; - } - - for (size_t i = 0; i < children_.size(); i++) - { - if (!children_[i]->Render(surface)) - { - return false; - } - } - - return true; - } - - - IMouseTracker* LayoutWidget::CreateMouseTracker(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::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, touches); - if (tracker != NULL) - { - return tracker; - } - } - - return NULL; - } - - - void LayoutWidget::RenderMouseOver(Orthanc::ImageAccessor& target, - int x, - int y) - { - for (size_t i = 0; i < children_.size(); i++) - { - children_[i]->RenderMouseOver(target, x, y); - } - } - - - void LayoutWidget::MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers) - { - for (size_t i = 0; i < children_.size(); i++) - { - children_[i]->MouseWheel(direction, x, y, modifiers); - } - } - - - void LayoutWidget::KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers) - { - for (size_t i = 0; i < children_.size(); i++) - { - children_[i]->GetWidget().KeyPressed(key, keyChar, modifiers); - } - } - - - void LayoutWidget::DoAnimation() - { - if (hasAnimation_) - { - for (size_t i = 0; i < children_.size(); i++) - { - children_[i]->DoAnimation(); - } - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - bool LayoutWidget::HasRenderMouseOver() - { - for (size_t i = 0; i < children_.size(); i++) - { - if (children_[i]->HasRenderMouseOver()) - { - return true; - } - } - - return false; - } -}
--- a/Framework/Widgets/LayoutWidget.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +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 "WidgetBase.h" - -#include <vector> -#include <memory> - -namespace Deprecated -{ - class LayoutWidget : public WidgetBase - { - private: - class LayoutMouseTracker; - class ChildWidget; - - std::vector<ChildWidget*> children_; - bool isHorizontal_; - unsigned int width_; - unsigned int height_; - std::auto_ptr<IMouseTracker> mouseTracker_; - unsigned int paddingLeft_; - unsigned int paddingTop_; - unsigned int paddingRight_; - unsigned int paddingBottom_; - unsigned int paddingInternal_; - bool hasAnimation_; - - void ComputeChildrenExtents(); - - public: - LayoutWidget(const std::string& name); - - virtual ~LayoutWidget(); - - virtual void FitContent(); - - virtual void NotifyContentChanged(const IWidget& widget); - - void SetHorizontal(); - - void SetVertical(); - - void SetPadding(unsigned int left, - unsigned int top, - unsigned int right, - unsigned int bottom, - unsigned int spacing); - - void SetPadding(unsigned int padding); - - unsigned int GetPaddingLeft() const - { - return paddingLeft_; - } - - unsigned int GetPaddingTop() const - { - return paddingTop_; - } - - unsigned int GetPaddingRight() const - { - return paddingRight_; - } - - unsigned int GetPaddingBottom() const - { - return paddingBottom_; - } - - unsigned int GetPaddingInternal() const - { - return paddingInternal_; - } - - IWidget& AddWidget(IWidget* widget); // Takes ownership - - virtual void SetStatusBar(IStatusBar& statusBar); - - virtual void SetSize(unsigned int width, - unsigned int height); - - virtual bool Render(Orthanc::ImageAccessor& surface); - - virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& touches); - - virtual void RenderMouseOver(Orthanc::ImageAccessor& target, - int x, - int y); - - virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers); - - virtual void KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers); - - virtual bool HasAnimation() const - { - return hasAnimation_; - } - - virtual void DoAnimation(); - - virtual bool HasRenderMouseOver(); - }; -}
--- a/Framework/Widgets/PanMouseTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +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 "PanMouseTracker.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> - -namespace Deprecated -{ - PanMouseTracker::PanMouseTracker(WorldSceneWidget& that, - int x, - int y) : - that_(that) - { - that.GetView().GetPan(originalPanX_, originalPanY_); - that.GetView().MapPixelCenterToScene(downX_, downY_, x, y); - } - - - void PanMouseTracker::Render(OrthancStone::CairoContext& context, - double zoom) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - - void PanMouseTracker::MouseMove(int displayX, - int displayY, - double x, - double y, - const std::vector<Touch>& displayTouches, - const std::vector<Touch>& sceneTouches) - { - ViewportGeometry view = that_.GetView(); - view.SetPan(originalPanX_ + (x - downX_) * view.GetZoom(), - originalPanY_ + (y - downY_) * view.GetZoom()); - that_.SetView(view); - } -}
--- a/Framework/Widgets/PanMouseTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +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 "WorldSceneWidget.h" - -namespace Deprecated -{ - class PanMouseTracker : public IWorldSceneMouseTracker - { - private: - WorldSceneWidget& that_; - double originalPanX_; - double originalPanY_; - double downX_; - double downY_; - - public: - PanMouseTracker(WorldSceneWidget& that, - int x, - int y); - - virtual bool HasRender() const - { - return false; - } - - virtual void MouseUp() - { - } - - virtual void Render(OrthancStone::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/PanZoomMouseTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +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 "PanZoomMouseTracker.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> -#include <math.h> - -namespace Deprecated -{ - 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(OrthancStone::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); - } -}
--- a/Framework/Widgets/PanZoomMouseTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +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 "WorldSceneWidget.h" - -namespace Deprecated -{ - 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(OrthancStone::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.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,654 +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 "SliceViewerWidget.h" - -#include "../Layers/SliceOutlineRenderer.h" -#include "../Toolbox/GeometryToolbox.h" -#include "Framework/Layers/FrameRenderer.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> - -#include <boost/math/constants/constants.hpp> - - -static const double THIN_SLICE_THICKNESS = 100.0 * std::numeric_limits<double>::epsilon(); - -namespace Deprecated -{ - class SliceViewerWidget::Scene : public boost::noncopyable - { - private: - OrthancStone::CoordinateSystem3D plane_; - double thickness_; - size_t countMissing_; - std::vector<ILayerRenderer*> renderers_; - - public: - void DeleteLayer(size_t index) - { - if (index >= renderers_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - assert(countMissing_ <= renderers_.size()); - - if (renderers_[index] != NULL) - { - assert(countMissing_ < renderers_.size()); - delete renderers_[index]; - renderers_[index] = NULL; - countMissing_++; - } - } - - Scene(const OrthancStone::CoordinateSystem3D& plane, - double thickness, - size_t countLayers) : - plane_(plane), - thickness_(thickness), - countMissing_(countLayers), - renderers_(countLayers, NULL) - { - if (thickness <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - ~Scene() - { - for (size_t i = 0; i < renderers_.size(); i++) - { - DeleteLayer(i); - } - } - - void SetLayer(size_t index, - ILayerRenderer* renderer) // Takes ownership - { - if (renderer == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - DeleteLayer(index); - - renderers_[index] = renderer; - countMissing_--; - } - - const OrthancStone::CoordinateSystem3D& GetPlane() const - { - return plane_; - } - - bool HasRenderer(size_t index) - { - return renderers_[index] != NULL; - } - - bool IsComplete() const - { - return countMissing_ == 0; - } - - unsigned int GetCountMissing() const - { - return static_cast<unsigned int>(countMissing_); - } - - bool RenderScene(OrthancStone::CairoContext& context, - const ViewportGeometry& view, - const OrthancStone::CoordinateSystem3D& viewportPlane) - { - bool fullQuality = true; - cairo_t *cr = context.GetObject(); - - for (size_t i = 0; i < renderers_.size(); i++) - { - if (renderers_[i] != NULL) - { - const OrthancStone::CoordinateSystem3D& framePlane = renderers_[i]->GetLayerPlane(); - - double x0, y0, x1, y1, x2, y2; - viewportPlane.ProjectPoint(x0, y0, framePlane.GetOrigin()); - viewportPlane.ProjectPoint(x1, y1, framePlane.GetOrigin() + framePlane.GetAxisX()); - viewportPlane.ProjectPoint(x2, y2, framePlane.GetOrigin() + framePlane.GetAxisY()); - - /** - * Now we solve the system of linear equations Ax + b = x', given: - * A [0 ; 0] + b = [x0 ; y0] - * A [1 ; 0] + b = [x1 ; y1] - * A [0 ; 1] + b = [x2 ; y2] - * <=> - * b = [x0 ; y0] - * A [1 ; 0] = [x1 ; y1] - b = [x1 - x0 ; y1 - y0] - * A [0 ; 1] = [x2 ; y2] - b = [x2 - x0 ; y2 - y0] - * <=> - * b = [x0 ; y0] - * [a11 ; a21] = [x1 - x0 ; y1 - y0] - * [a12 ; a22] = [x2 - x0 ; y2 - y0] - **/ - - cairo_matrix_t transform; - cairo_matrix_init(&transform, x1 - x0, y1 - y0, x2 - x0, y2 - y0, x0, y0); - - cairo_save(cr); - cairo_transform(cr, &transform); - - if (!renderers_[i]->RenderLayer(context, view)) - { - cairo_restore(cr); - return false; - } - - cairo_restore(cr); - } - - if (renderers_[i] != NULL && - !renderers_[i]->IsFullQuality()) - { - fullQuality = false; - } - } - - if (!fullQuality) - { - double x, y; - view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10); - - cairo_translate(cr, x, y); - -#if 1 - double s = 5.0 / view.GetZoom(); - cairo_rectangle(cr, -s, -s, 2.0 * s, 2.0 * s); -#else - // TODO Drawing filled circles makes WebAssembly crash! - cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2.0 * boost::math::constants::pi<double>()); -#endif - - cairo_set_line_width(cr, 2.0 / view.GetZoom()); - cairo_set_source_rgb(cr, 1, 1, 1); - cairo_stroke_preserve(cr); - cairo_set_source_rgb(cr, 1, 0, 0); - cairo_fill(cr); - } - - return true; - } - - void SetLayerStyle(size_t index, - const RenderStyle& style) - { - if (renderers_[index] != NULL) - { - renderers_[index]->SetLayerStyle(style); - } - } - - bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const - { - bool isOpposite; - if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, - plane.GetNormal(), - plane_.GetNormal())) - { - return false; - } - else - { - double z = (plane_.ProjectAlongNormal(plane.GetOrigin()) - - plane_.ProjectAlongNormal(plane_.GetOrigin())); - - if (z < 0) - { - z = -z; - } - - return z <= thickness_; - } - } - - double GetThickness() const - { - return thickness_; - } - }; - - - bool SliceViewerWidget::LookupLayer(size_t& index /* out */, - const IVolumeSlicer& layer) const - { - LayersIndex::const_iterator found = layersIndex_.find(&layer); - - if (found == layersIndex_.end()) - { - return false; - } - else - { - index = found->second; - assert(index < layers_.size() && - layers_[index] == &layer); - return true; - } - } - - - void SliceViewerWidget::GetLayerExtent(OrthancStone::Extent2D& extent, - IVolumeSlicer& source) const - { - extent.Reset(); - - std::vector<OrthancStone::Vector> points; - if (source.GetExtent(points, plane_)) - { - for (size_t i = 0; i < points.size(); i++) - { - double x, y; - plane_.ProjectPoint(x, y, points[i]); - extent.AddPoint(x, y); - } - } - } - - - OrthancStone::Extent2D SliceViewerWidget::GetSceneExtent() - { - OrthancStone::Extent2D sceneExtent; - - for (size_t i = 0; i < layers_.size(); i++) - { - assert(layers_[i] != NULL); - OrthancStone::Extent2D layerExtent; - GetLayerExtent(layerExtent, *layers_[i]); - - sceneExtent.Union(layerExtent); - } - - return sceneExtent; - } - - - bool SliceViewerWidget::RenderScene(OrthancStone::CairoContext& context, - const ViewportGeometry& view) - { - if (currentScene_.get() != NULL) - { - return currentScene_->RenderScene(context, view, plane_); - } - else - { - return true; - } - } - - - void SliceViewerWidget::ResetPendingScene() - { - double thickness; - if (pendingScene_.get() == NULL) - { - thickness = 1.0; - } - else - { - thickness = pendingScene_->GetThickness(); - } - - pendingScene_.reset(new Scene(plane_, thickness, layers_.size())); - } - - - void SliceViewerWidget::UpdateLayer(size_t index, - ILayerRenderer* renderer, - const OrthancStone::CoordinateSystem3D& plane) - { - LOG(INFO) << "Updating layer " << index; - - std::auto_ptr<ILayerRenderer> tmp(renderer); - - if (renderer == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - if (index >= layers_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - assert(layers_.size() == styles_.size()); - renderer->SetLayerStyle(styles_[index]); - - if (currentScene_.get() != NULL && - currentScene_->ContainsPlane(plane)) - { - currentScene_->SetLayer(index, tmp.release()); - NotifyContentChanged(); - } - else if (pendingScene_.get() != NULL && - pendingScene_->ContainsPlane(plane)) - { - pendingScene_->SetLayer(index, tmp.release()); - - if (currentScene_.get() == NULL || - !currentScene_->IsComplete() || - pendingScene_->IsComplete()) - { - currentScene_ = pendingScene_; - NotifyContentChanged(); - } - } - } - - - SliceViewerWidget::SliceViewerWidget(OrthancStone::MessageBroker& broker, - const std::string& name) : - WorldSceneWidget(name), - IObserver(broker), - IObservable(broker), - started_(false) - { - SetBackgroundCleared(true); - } - - - SliceViewerWidget::~SliceViewerWidget() - { - for (size_t i = 0; i < layers_.size(); i++) - { - delete layers_[i]; - } - } - - void SliceViewerWidget::ObserveLayer(IVolumeSlicer& layer) - { - layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::GeometryReadyMessage> - (*this, &SliceViewerWidget::OnGeometryReady)); - // currently ignore errors layer->RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::GeometryErrorMessage>(*this, &SliceViewerWidget::...)); - layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::SliceContentChangedMessage> - (*this, &SliceViewerWidget::OnSliceChanged)); - layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::ContentChangedMessage> - (*this, &SliceViewerWidget::OnContentChanged)); - layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerReadyMessage> - (*this, &SliceViewerWidget::OnLayerReady)); - layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerErrorMessage> - (*this, &SliceViewerWidget::OnLayerError)); - } - - - size_t SliceViewerWidget::AddLayer(IVolumeSlicer* layer) // Takes ownership - { - if (layer == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - size_t index = layers_.size(); - layers_.push_back(layer); - styles_.push_back(RenderStyle()); - layersIndex_[layer] = index; - - ResetPendingScene(); - - ObserveLayer(*layer); - - ResetChangedLayers(); - - return index; - } - - - void SliceViewerWidget::ReplaceLayer(size_t index, IVolumeSlicer* layer) // Takes ownership - { - if (layer == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - if (index >= layers_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - delete layers_[index]; - layers_[index] = layer; - layersIndex_[layer] = index; - - ResetPendingScene(); - - ObserveLayer(*layer); - - InvalidateLayer(index); - } - - - void SliceViewerWidget::RemoveLayer(size_t index) - { - if (index >= layers_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - IVolumeSlicer* previousLayer = layers_[index]; - layersIndex_.erase(layersIndex_.find(previousLayer)); - layers_.erase(layers_.begin() + index); - changedLayers_.erase(changedLayers_.begin() + index); - styles_.erase(styles_.begin() + index); - - delete layers_[index]; - - currentScene_->DeleteLayer(index); - ResetPendingScene(); - - NotifyContentChanged(); - } - - - const RenderStyle& SliceViewerWidget::GetLayerStyle(size_t layer) const - { - if (layer >= layers_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - assert(layers_.size() == styles_.size()); - return styles_[layer]; - } - - - void SliceViewerWidget::SetLayerStyle(size_t layer, - const RenderStyle& style) - { - if (layer >= layers_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - assert(layers_.size() == styles_.size()); - styles_[layer] = style; - - if (currentScene_.get() != NULL) - { - currentScene_->SetLayerStyle(layer, style); - } - - if (pendingScene_.get() != NULL) - { - pendingScene_->SetLayerStyle(layer, style); - } - - NotifyContentChanged(); - } - - - void SliceViewerWidget::SetSlice(const OrthancStone::CoordinateSystem3D& plane) - { - LOG(INFO) << "Setting slice origin: (" << plane.GetOrigin()[0] - << "," << plane.GetOrigin()[1] - << "," << plane.GetOrigin()[2] << ")"; - - Deprecated::Slice displayedSlice(plane_, THIN_SLICE_THICKNESS); - - //if (!displayedSlice.ContainsPlane(slice)) - { - if (currentScene_.get() == NULL || - (pendingScene_.get() != NULL && - pendingScene_->IsComplete())) - { - currentScene_ = pendingScene_; - } - - plane_ = plane; - ResetPendingScene(); - - InvalidateAllLayers(); // TODO Removing this line avoid loading twice the image in WASM - } - - BroadcastMessage(DisplayedSliceMessage(*this, displayedSlice)); - } - - - void SliceViewerWidget::OnGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message) - { - size_t i; - if (LookupLayer(i, message.GetOrigin())) - { - LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName(); - - changedLayers_[i] = true; - //layers_[i]->ScheduleLayerCreation(plane_); - } - BroadcastMessage(GeometryChangedMessage(*this)); - } - - - void SliceViewerWidget::InvalidateAllLayers() - { - for (size_t i = 0; i < layers_.size(); i++) - { - assert(layers_[i] != NULL); - changedLayers_[i] = true; - - //layers_[i]->ScheduleLayerCreation(plane_); - } - } - - - void SliceViewerWidget::InvalidateLayer(size_t layer) - { - if (layer >= layers_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - assert(layers_[layer] != NULL); - changedLayers_[layer] = true; - - //layers_[layer]->ScheduleLayerCreation(plane_); - } - - - void SliceViewerWidget::OnContentChanged(const IVolumeSlicer::ContentChangedMessage& message) - { - size_t index; - if (LookupLayer(index, message.GetOrigin())) - { - InvalidateLayer(index); - } - - BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); - } - - - void SliceViewerWidget::OnSliceChanged(const IVolumeSlicer::SliceContentChangedMessage& message) - { - if (message.GetSlice().ContainsPlane(plane_)) - { - size_t index; - if (LookupLayer(index, message.GetOrigin())) - { - InvalidateLayer(index); - } - } - - BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); - } - - - void SliceViewerWidget::OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message) - { - size_t index; - if (LookupLayer(index, message.GetOrigin())) - { - LOG(INFO) << "Renderer ready for layer " << index; - UpdateLayer(index, message.CreateRenderer(), message.GetSlice()); - } - - BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); - } - - - void SliceViewerWidget::OnLayerError(const IVolumeSlicer::LayerErrorMessage& message) - { - size_t index; - if (LookupLayer(index, message.GetOrigin())) - { - LOG(ERROR) << "Using error renderer on layer " << index; - - // TODO - //UpdateLayer(index, new SliceOutlineRenderer(slice), slice); - - BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); - } - } - - - void SliceViewerWidget::ResetChangedLayers() - { - changedLayers_.resize(layers_.size()); - - for (size_t i = 0; i < changedLayers_.size(); i++) - { - changedLayers_[i] = false; - } - } - - - void SliceViewerWidget::DoAnimation() - { - assert(changedLayers_.size() <= layers_.size()); - - for (size_t i = 0; i < changedLayers_.size(); i++) - { - if (changedLayers_[i]) - { - layers_[i]->ScheduleLayerCreation(plane_); - } - } - - ResetChangedLayers(); - } -}
--- a/Framework/Widgets/SliceViewerWidget.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +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 "WorldSceneWidget.h" -#include "../Layers/IVolumeSlicer.h" -#include "../Toolbox/Extent2D.h" -#include "../../Framework/Messages/IObserver.h" - -#include <map> - -namespace Deprecated -{ - class SliceViewerWidget : - public WorldSceneWidget, - public OrthancStone::IObserver, - public OrthancStone::IObservable - { - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryChangedMessage, SliceViewerWidget); - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentChangedMessage, SliceViewerWidget); - - - // TODO - Use this message in ReferenceLineSource - class DisplayedSliceMessage : public OrthancStone::OriginMessage<SliceViewerWidget> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const Deprecated::Slice& slice_; - - public: - DisplayedSliceMessage(SliceViewerWidget& origin, - const Deprecated::Slice& slice) : - OriginMessage(origin), - slice_(slice) - { - } - - const Deprecated::Slice& GetSlice() const - { - return slice_; - } - }; - - private: - SliceViewerWidget(const SliceViewerWidget&); - SliceViewerWidget& operator=(const SliceViewerWidget&); - - class Scene; - - typedef std::map<const IVolumeSlicer*, size_t> LayersIndex; - - bool started_; - LayersIndex layersIndex_; - std::vector<IVolumeSlicer*> layers_; - std::vector<RenderStyle> styles_; - OrthancStone::CoordinateSystem3D plane_; - std::auto_ptr<Scene> currentScene_; - std::auto_ptr<Scene> pendingScene_; - std::vector<bool> changedLayers_; - - bool LookupLayer(size_t& index /* out */, - const IVolumeSlicer& layer) const; - - void GetLayerExtent(OrthancStone::Extent2D& extent, - IVolumeSlicer& source) const; - - void OnGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message); - - virtual void OnContentChanged(const IVolumeSlicer::ContentChangedMessage& message); - - virtual void OnSliceChanged(const IVolumeSlicer::SliceContentChangedMessage& message); - - virtual void OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message); - - virtual void OnLayerError(const IVolumeSlicer::LayerErrorMessage& message); - - void ObserveLayer(IVolumeSlicer& source); - - void ResetChangedLayers(); - - public: - SliceViewerWidget(OrthancStone::MessageBroker& broker, - const std::string& name); - - virtual OrthancStone::Extent2D GetSceneExtent(); - - protected: - virtual bool RenderScene(OrthancStone::CairoContext& context, - const ViewportGeometry& view); - - void ResetPendingScene(); - - void UpdateLayer(size_t index, - ILayerRenderer* renderer, - const OrthancStone::CoordinateSystem3D& plane); - - void InvalidateAllLayers(); - - void InvalidateLayer(size_t layer); - - public: - virtual ~SliceViewerWidget(); - - size_t AddLayer(IVolumeSlicer* layer); // Takes ownership - - void ReplaceLayer(size_t layerIndex, IVolumeSlicer* layer); // Takes ownership - - void RemoveLayer(size_t layerIndex); - - size_t GetLayerCount() const - { - return layers_.size(); - } - - const RenderStyle& GetLayerStyle(size_t layer) const; - - void SetLayerStyle(size_t layer, - const RenderStyle& style); - - void SetSlice(const OrthancStone::CoordinateSystem3D& plane); - - const OrthancStone::CoordinateSystem3D& GetSlice() const - { - return plane_; - } - - virtual bool HasAnimation() const - { - return true; - } - - virtual void DoAnimation(); - }; -}
--- a/Framework/Widgets/TestCairoWidget.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +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 "TestCairoWidget.h" - -#include <stdio.h> - - -namespace Deprecated -{ - namespace Samples - { - void TestCairoWidget::DoAnimation() - { - value_ -= 0.01f; - if (value_ < 0) - { - value_ = 1; - } - - NotifyContentChanged(); - } - - - bool TestCairoWidget::RenderCairo(OrthancStone::CairoContext& context) - { - cairo_t* cr = context.GetObject(); - - cairo_set_source_rgb (cr, .3, 0, 0); - cairo_paint(cr); - - cairo_set_source_rgb(cr, 0, 1, 0); - cairo_rectangle(cr, width_ / 4, height_ / 4, width_ / 2, height_ / 2); - cairo_set_line_width(cr, 1.0); - cairo_fill(cr); - - cairo_set_source_rgb(cr, 0, 1, value_); - cairo_rectangle(cr, width_ / 2 - 50, height_ / 2 - 50, 100, 100); - cairo_fill(cr); - - return true; - } - - - void TestCairoWidget::RenderMouseOverCairo(OrthancStone::CairoContext& context, - int x, - int y) - { - cairo_t* cr = context.GetObject(); - - cairo_set_source_rgb (cr, 1, 0, 0); - cairo_rectangle(cr, x - 5, y - 5, 10, 10); - cairo_set_line_width(cr, 1.0); - cairo_stroke(cr); - - char buf[64]; - sprintf(buf, "(%d,%d)", x, y); - UpdateStatusBar(buf); - } - - - TestCairoWidget::TestCairoWidget(const std::string& name, bool animate) : - CairoWidget(name), - width_(0), - height_(0), - value_(1), - animate_(animate) - { - } - - - void TestCairoWidget::SetSize(unsigned int width, - unsigned int height) - { - CairoWidget::SetSize(width, height); - width_ = width; - height_ = height; - } - - - IMouseTracker* TestCairoWidget::CreateMouseTracker(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& touches) - { - UpdateStatusBar("Click"); - return NULL; - } - - - void TestCairoWidget::MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers) - { - UpdateStatusBar(direction == OrthancStone::MouseWheelDirection_Down ? "Wheel down" : "Wheel up"); - } - - - void TestCairoWidget::KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers) - { - UpdateStatusBar("Key pressed: \"" + std::string(1, keyChar) + "\""); - } - } -}
--- a/Framework/Widgets/TestCairoWidget.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +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 "CairoWidget.h" - -namespace Deprecated -{ - namespace Samples - { - class TestCairoWidget : public CairoWidget - { - private: - unsigned int width_; - unsigned int height_; - float value_; - bool animate_; - - protected: - virtual bool RenderCairo(OrthancStone::CairoContext& context); - - virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context, - int x, - int y); - - public: - TestCairoWidget(const std::string& name, bool animate); - - virtual void SetSize(unsigned int width, - unsigned int height); - - virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& touches); - - virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers); - - virtual void KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers); - - virtual bool HasAnimation() const - { - return animate_; - } - - virtual void DoAnimation(); - - virtual bool HasRenderMouseOver() - { - return true; - } - }; - } -}
--- a/Framework/Widgets/TestWorldSceneWidget.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +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 "TestWorldSceneWidget.h" - -#include <Core/OrthancException.h> - -#include <math.h> -#include <stdio.h> - -namespace Deprecated -{ - namespace Samples - { - class TestWorldSceneWidget::Interactor : public IWorldSceneInteractor - { - public: - virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, - const ViewportGeometry& view, - OrthancStone::MouseButton button, - OrthancStone::KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - IStatusBar* statusBar, - const std::vector<Touch>& touches) - { - if (statusBar) - { - char buf[64]; - sprintf(buf, "X = %0.2f, Y = %0.2f", x, y); - statusBar->SetMessage(buf); - } - - return NULL; - } - - virtual void MouseOver(OrthancStone::CairoContext& context, - WorldSceneWidget& widget, - const ViewportGeometry& view, - double x, - double y, - IStatusBar* statusBar) - { - double S = 0.5; - - if (fabs(x) <= S && - fabs(y) <= S) - { - cairo_t* cr = context.GetObject(); - cairo_set_source_rgb(cr, 1, 0, 0); - cairo_rectangle(cr, -S, -S , 2.0 * S, 2.0 * S); - cairo_set_line_width(cr, 1.0 / view.GetZoom()); - cairo_stroke(cr); - } - } - - virtual void MouseWheel(WorldSceneWidget& widget, - OrthancStone::MouseWheelDirection direction, - OrthancStone::KeyboardModifiers modifiers, - IStatusBar* statusBar) - { - if (statusBar) - { - statusBar->SetMessage(direction == OrthancStone::MouseWheelDirection_Down ? "Wheel down" : "Wheel up"); - } - } - - virtual void KeyPressed(WorldSceneWidget& widget, - OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers, - IStatusBar* statusBar) - { - if (statusBar) - { - statusBar->SetMessage("Key pressed: \"" + std::string(1, keyChar) + "\""); - } - } - }; - - - bool TestWorldSceneWidget::RenderScene(OrthancStone::CairoContext& context, - const ViewportGeometry& view) - { - cairo_t* cr = context.GetObject(); - - // Clear background - cairo_set_source_rgb(cr, 0, 0, 0); - cairo_paint(cr); - - float color = static_cast<float>(count_ % 16) / 15.0f; - cairo_set_source_rgb(cr, 0, 1.0f - color, color); - cairo_rectangle(cr, -10, -.5, 20, 1); - cairo_fill(cr); - - return true; - } - - - TestWorldSceneWidget::TestWorldSceneWidget(const std::string& name, bool animate) : - WorldSceneWidget(name), - interactor_(new Interactor), - animate_(animate), - count_(0) - { - SetInteractor(*interactor_); - } - - - OrthancStone::Extent2D TestWorldSceneWidget::GetSceneExtent() - { - return OrthancStone::Extent2D(-10, -.5, 10, .5); - } - - - void TestWorldSceneWidget::DoAnimation() - { - if (animate_) - { - count_++; - NotifyContentChanged(); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - } -}
--- a/Framework/Widgets/TestWorldSceneWidget.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +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 "WorldSceneWidget.h" - -#include <memory> - -namespace Deprecated -{ - namespace Samples - { - class TestWorldSceneWidget : public WorldSceneWidget - { - private: - class Interactor; - - std::auto_ptr<Interactor> interactor_; - bool animate_; - unsigned int count_; - - protected: - virtual bool RenderScene(OrthancStone::CairoContext& context, - const ViewportGeometry& view); - - public: - TestWorldSceneWidget(const std::string& name, bool animate); - - virtual OrthancStone::Extent2D GetSceneExtent(); - - virtual bool HasAnimation() const - { - return animate_; - } - - virtual void DoAnimation(); - - virtual bool HasRenderMouseOver() - { - return true; - } - }; - } -}
--- a/Framework/Widgets/WidgetBase.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +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 "WidgetBase.h" - -#include <Core/OrthancException.h> -#include <Core/Images/ImageProcessing.h> -#include <Core/Logging.h> - -namespace Deprecated -{ - void WidgetBase::NotifyContentChanged() - { - if (parent_ != NULL) - { - parent_->NotifyContentChanged(); - } - - if (viewport_ != NULL) - { - viewport_->NotifyBackgroundChanged(); - } - } - - - void WidgetBase::SetParent(IWidget& parent) - { - if (parent_ != NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - parent_ = &parent; - } - } - - - void WidgetBase::ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const - { - // Clear the background using Orthanc - - if (backgroundCleared_) - { - Orthanc::ImageProcessing::Set(target, - backgroundColor_[0], - backgroundColor_[1], - backgroundColor_[2], - 255 /* alpha */); - } - } - - - void WidgetBase::ClearBackgroundCairo(OrthancStone::CairoContext& context) const - { - // Clear the background using Cairo - - if (IsBackgroundCleared()) - { - uint8_t red, green, blue; - GetBackgroundColor(red, green, blue); - - context.SetSourceColor(red, green, blue); - cairo_paint(context.GetObject()); - } - } - - - void WidgetBase::ClearBackgroundCairo(Orthanc::ImageAccessor& target) const - { - OrthancStone::CairoSurface surface(target, false /* no alpha */); - OrthancStone::CairoContext context(surface); - ClearBackgroundCairo(context); - } - - - void WidgetBase::UpdateStatusBar(const std::string& message) - { - if (statusBar_ != NULL) - { - statusBar_->SetMessage(message); - } - } - - - WidgetBase::WidgetBase(const std::string& name) : - parent_(NULL), - viewport_(NULL), - statusBar_(NULL), - backgroundCleared_(false), - transmitMouseOver_(false), - name_(name) - { - backgroundColor_[0] = 0; - backgroundColor_[1] = 0; - backgroundColor_[2] = 0; - } - - - void WidgetBase::SetViewport(WidgetViewport& viewport) - { - if (viewport_ != NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - viewport_ = &viewport; - } - } - - - void WidgetBase::SetBackgroundColor(uint8_t red, - uint8_t green, - uint8_t blue) - { - backgroundColor_[0] = red; - backgroundColor_[1] = green; - backgroundColor_[2] = blue; - } - - void WidgetBase::GetBackgroundColor(uint8_t& red, - uint8_t& green, - uint8_t& blue) const - { - red = backgroundColor_[0]; - green = backgroundColor_[1]; - blue = backgroundColor_[2]; - } - - - bool WidgetBase::Render(Orthanc::ImageAccessor& surface) - { -#if 0 - ClearBackgroundOrthanc(surface); -#else - ClearBackgroundCairo(surface); // Faster than Orthanc -#endif - - return true; - } - - - void WidgetBase::DoAnimation() - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } -}
--- a/Framework/Widgets/WidgetBase.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +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 "IWidget.h" - -#include "../Viewport/CairoContext.h" -#include "../Viewport/WidgetViewport.h" - -namespace Deprecated -{ - class WidgetBase : public IWidget - { - private: - IWidget* parent_; - WidgetViewport* viewport_; - IStatusBar* statusBar_; - bool backgroundCleared_; - uint8_t backgroundColor_[3]; - bool transmitMouseOver_; - std::string name_; - - protected: - void ClearBackgroundOrthanc(Orthanc::ImageAccessor& target) const; - - void ClearBackgroundCairo(OrthancStone::CairoContext& context) const; - - void ClearBackgroundCairo(Orthanc::ImageAccessor& target) const; - - void UpdateStatusBar(const std::string& message); - - IStatusBar* GetStatusBar() const - { - return statusBar_; - } - - public: - WidgetBase(const std::string& name); - - virtual void FitContent() - { - } - - virtual void SetParent(IWidget& parent); - - virtual void SetViewport(WidgetViewport& viewport); - - void SetBackgroundCleared(bool clear) - { - backgroundCleared_ = clear; - } - - bool IsBackgroundCleared() const - { - return backgroundCleared_; - } - - void SetTransmitMouseOver(bool transmit) - { - transmitMouseOver_ = transmit; - } - - void SetBackgroundColor(uint8_t red, - uint8_t green, - uint8_t blue); - - void GetBackgroundColor(uint8_t& red, - uint8_t& green, - uint8_t& blue) const; - - virtual void SetStatusBar(IStatusBar& statusBar) - { - statusBar_ = &statusBar; - } - - virtual bool Render(Orthanc::ImageAccessor& surface); - - virtual bool HasAnimation() const - { - return false; - } - - virtual void DoAnimation(); - - virtual bool HasRenderMouseOver() - { - return transmitMouseOver_; - } - - virtual void NotifyContentChanged(); - - const std::string& GetName() const - { - return name_; - } - - }; -}
--- a/Framework/Widgets/WorldSceneWidget.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,231 +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 "WorldSceneWidget.h" - -#include "PanMouseTracker.h" -#include "ZoomMouseTracker.h" -#include "PanZoomMouseTracker.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> - -#include <math.h> -#include <memory> -#include <cassert> - -namespace Deprecated -{ - // this is an adapter between a IWorldSceneMouseTracker - // that is tracking a mouse in scene coordinates/mm and - // an IMouseTracker that is tracking a mouse - // in screen coordinates/pixels. - class WorldSceneWidget::SceneMouseTracker : public IMouseTracker - { - private: - ViewportGeometry view_; - std::auto_ptr<IWorldSceneMouseTracker> tracker_; - - public: - SceneMouseTracker(const ViewportGeometry& view, - IWorldSceneMouseTracker* tracker) : - view_(view), - tracker_(tracker) - { - if (tracker == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - virtual void Render(Orthanc::ImageAccessor& target) - { - if (tracker_->HasRender()) - { - OrthancStone::CairoSurface surface(target, false /* no alpha */); - OrthancStone::CairoContext context(surface); - view_.ApplyTransform(context); - tracker_->Render(context, view_.GetZoom()); - } - } - - virtual void MouseUp() - { - tracker_->MouseUp(); - } - - virtual void MouseMove(int x, - int y, - const std::vector<Touch>& displayTouches) - { - double sceneX, sceneY; - view_.MapPixelCenterToScene(sceneX, sceneY, x, y); - - 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(static_cast<float>(sx), static_cast<float>(sy))); - } - tracker_->MouseMove(x, y, sceneX, sceneY, displayTouches, sceneTouches); - } - }; - - - bool WorldSceneWidget::RenderCairo(OrthancStone::CairoContext& context) - { - view_.ApplyTransform(context); - return RenderScene(context, view_); - } - - - void WorldSceneWidget::RenderMouseOverCairo(OrthancStone::CairoContext& context, - int x, - int y) - { - ViewportGeometry view = GetView(); - view.ApplyTransform(context); - - double sceneX, sceneY; - view.MapPixelCenterToScene(sceneX, sceneY, x, y); - - if (interactor_) - { - interactor_->MouseOver(context, *this, view, sceneX, sceneY, GetStatusBar()); - } - } - - - void WorldSceneWidget::SetSceneExtent(ViewportGeometry& view) - { - view.SetSceneExtent(GetSceneExtent()); - } - - - void WorldSceneWidget::SetSize(unsigned int width, - unsigned int height) - { - CairoWidget::SetSize(width, height); - view_.SetDisplaySize(width, height); - } - - - void WorldSceneWidget::SetInteractor(IWorldSceneInteractor& interactor) - { - interactor_ = &interactor; - } - - - void WorldSceneWidget::FitContent() - { - SetSceneExtent(view_); - view_.FitContent(); - - NotifyContentChanged(); - } - - - void WorldSceneWidget::SetView(const ViewportGeometry& view) - { - view_ = view; - - NotifyContentChanged(); - } - - - IMouseTracker* WorldSceneWidget::CreateMouseTracker(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& touches) - { - double sceneX, sceneY; - view_.MapPixelCenterToScene(sceneX, sceneY, x, y); - - // asks the Widget Interactor to provide a mouse tracker - std::auto_ptr<IWorldSceneMouseTracker> tracker; - - if (interactor_) - { - tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar(), touches)); - } - - if (tracker.get() != NULL) - { - return new SceneMouseTracker(view_, tracker.release()); - } - else if (hasDefaultMouseEvents_) - { - printf("has default mouse events\n"); - if (touches.size() == 2) - { - printf("2 touches !\n"); - return new SceneMouseTracker(view_, new PanZoomMouseTracker(*this, touches)); - } - else - { - switch (button) - { - case OrthancStone::MouseButton_Middle: - return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y)); - - case OrthancStone::MouseButton_Right: - return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y)); - - default: - return NULL; - } - } - } - else - { - return NULL; - } - } - - - void WorldSceneWidget::MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers) - { - if (interactor_) - { - interactor_->MouseWheel(*this, direction, modifiers, GetStatusBar()); - } - } - - - void WorldSceneWidget::KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers) - { - if (interactor_) - { - interactor_->KeyPressed(*this, key, keyChar, modifiers, GetStatusBar()); - } - } -}
--- a/Framework/Widgets/WorldSceneWidget.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +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 "CairoWidget.h" -#include "IWorldSceneInteractor.h" - -#include "../Toolbox/ViewportGeometry.h" - -namespace Deprecated -{ - class WorldSceneWidget : public CairoWidget - { - private: - class SceneMouseTracker; - - ViewportGeometry view_; - IWorldSceneInteractor* interactor_; - bool hasDefaultMouseEvents_; - - protected: - virtual OrthancStone::Extent2D GetSceneExtent() = 0; - - virtual bool RenderScene(OrthancStone::CairoContext& context, - const ViewportGeometry& view) = 0; - - // From CairoWidget - virtual bool RenderCairo(OrthancStone::CairoContext& context); - - // From CairoWidget - virtual void RenderMouseOverCairo(OrthancStone::CairoContext& context, - int x, - int y); - - void SetSceneExtent(ViewportGeometry& geometry); - - public: - WorldSceneWidget(const std::string& name) : - CairoWidget(name), - interactor_(NULL), - hasDefaultMouseEvents_(true) - { - } - - void SetDefaultMouseEvents(bool value) - { - hasDefaultMouseEvents_ = value; - } - - bool HasDefaultMouseEvents() const - { - return hasDefaultMouseEvents_; - } - - void SetInteractor(IWorldSceneInteractor& interactor); - - void SetView(const ViewportGeometry& view); - - const ViewportGeometry& GetView() const - { - return view_; - } - - virtual void SetSize(unsigned int width, - unsigned int height); - - virtual void FitContent(); - - virtual IMouseTracker* CreateMouseTracker(OrthancStone::MouseButton button, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers, - const std::vector<Touch>& touches); - - virtual void MouseWheel(OrthancStone::MouseWheelDirection direction, - int x, - int y, - OrthancStone::KeyboardModifiers modifiers); - - virtual void KeyPressed(OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers); - }; -}
--- a/Framework/Widgets/ZoomMouseTracker.cpp Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +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 "ZoomMouseTracker.h" - -#include <Core/Logging.h> -#include <Core/OrthancException.h> - -namespace Deprecated -{ - ZoomMouseTracker::ZoomMouseTracker(WorldSceneWidget& that, - int x, - int y) : - that_(that), - originalZoom_(that.GetView().GetZoom()), - downX_(x), - downY_(y) - { - that.GetView().MapPixelCenterToScene(centerX_, centerY_, x, 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 ZoomMouseTracker::Render(OrthancStone::CairoContext& context, - double zoom) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - - void ZoomMouseTracker::MouseMove(int displayX, - int displayY, - double x, - double y, - const std::vector<Touch>& displayTouches, - const std::vector<Touch>& sceneTouches) - { - static const double MIN_ZOOM = -4; - static const double MAX_ZOOM = 4; - - - if (!idle_) - { - double dy = static_cast<double>(displayY - downY_) * 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); - - ViewportGeometry view = that_.GetView(); - - view.SetZoom(z * originalZoom_); - - // Correct the pan so that the original click point is kept at - // the same location on the display - double panX, panY; - view.GetPan(panX, panY); - - int tx, ty; - view.MapSceneToDisplay(tx, ty, centerX_, centerY_); - view.SetPan(panX + static_cast<double>(downX_ - tx), - panY + static_cast<double>(downY_ - ty)); - - that_.SetView(view); - } - } -}
--- a/Framework/Widgets/ZoomMouseTracker.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +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 "WorldSceneWidget.h" - -namespace Deprecated -{ - class ZoomMouseTracker : public IWorldSceneMouseTracker - { - private: - WorldSceneWidget& that_; - double originalZoom_; - int downX_; - int downY_; - double centerX_; - double centerY_; - bool idle_; - double normalization_; - - public: - ZoomMouseTracker(WorldSceneWidget& that, - int x, - int y); - - virtual bool HasRender() const - { - return false; - } - - virtual void MouseUp() - { - } - - virtual void Render(OrthancStone::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); - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Wrappers/CairoContext.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,146 @@ +/** + * 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 "CairoContext.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> + + +namespace OrthancStone +{ + CairoContext::CairoContext(CairoSurface& surface) : + width_(surface.GetWidth()), + height_(surface.GetHeight()) + { + context_ = cairo_create(surface.GetObject()); + if (!context_) + { + LOG(ERROR) << "Cannot create Cairo drawing context"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + CairoContext::~CairoContext() + { + if (context_ != NULL) + { + cairo_destroy(context_); + context_ = NULL; + } + } + + + void CairoContext::SetSourceColor(uint8_t red, + uint8_t green, + uint8_t blue) + { + cairo_set_source_rgb(context_, + static_cast<float>(red) / 255.0f, + static_cast<float>(green) / 255.0f, + static_cast<float>(blue) / 255.0f); + } + + + class CairoContext::AlphaSurface : public boost::noncopyable + { + private: + cairo_surface_t *surface_; + + public: + AlphaSurface(unsigned int width, + unsigned int height) + { + surface_ = cairo_image_surface_create(CAIRO_FORMAT_A8, width, height); + + if (!surface_) + { + // Should never occur + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS) + { + LOG(ERROR) << "Cannot create a Cairo surface"; + cairo_surface_destroy(surface_); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + ~AlphaSurface() + { + cairo_surface_destroy(surface_); + } + + void GetAccessor(Orthanc::ImageAccessor& target) + { + target.AssignWritable(Orthanc::PixelFormat_Grayscale8, + cairo_image_surface_get_width(surface_), + cairo_image_surface_get_height(surface_), + cairo_image_surface_get_stride(surface_), + cairo_image_surface_get_data(surface_)); + } + + void Blit(cairo_t* cr, + double x, + double y) + { + cairo_surface_mark_dirty(surface_); + cairo_mask_surface(cr, surface_, x, y); + cairo_fill(cr); + } + }; + + + void CairoContext::DrawText(const Orthanc::Font& font, + const std::string& text, + double x, + double y, + BitmapAnchor anchor) + { + // Render a bitmap containing the text + unsigned int width, height; + font.ComputeTextExtent(width, height, text); + + AlphaSurface surface(width, height); + + Orthanc::ImageAccessor accessor; + surface.GetAccessor(accessor); + font.Draw(accessor, text, 0, 0, 255); + + // Correct the text location given the anchor location + double deltaX, deltaY; + ComputeAnchorTranslation(deltaX, deltaY, anchor, width, height); + + // Cancel zoom/rotation before blitting the text onto the surface + double pixelX = x; + double pixelY = y; + cairo_user_to_device(context_, &pixelX, &pixelY); + + cairo_save(context_); + cairo_identity_matrix(context_); + + // Blit the text bitmap + surface.Blit(context_, pixelX + deltaX, pixelY + deltaY); + cairo_restore(context_); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Wrappers/CairoContext.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,76 @@ +/** + * 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 "CairoSurface.h" +#include "../StoneEnumerations.h" + +#include <Core/Images/Font.h> + +namespace OrthancStone +{ + // This is a RAII wrapper around the Cairo drawing context + class CairoContext : public boost::noncopyable + { + private: + class AlphaSurface; + + cairo_t* context_; + unsigned int width_; + unsigned int height_; + + public: + CairoContext(CairoSurface& surface); + + ~CairoContext(); + + cairo_t* GetObject() + { + return context_; + } + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + void SetSourceColor(uint8_t red, + uint8_t green, + uint8_t blue); + + void SetSourceColor(const uint8_t color[3]) + { + SetSourceColor(color[0], color[1], color[2]); + } + + void DrawText(const Orthanc::Font& font, + const std::string& text, + double x, + double y, + BitmapAnchor anchor); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Wrappers/CairoSurface.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,155 @@ +/** + * 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 "CairoSurface.h" + +#include <Core/Logging.h> +#include <Core/OrthancException.h> +#include <Core/Images/ImageProcessing.h> + +namespace OrthancStone +{ + void CairoSurface::Release() + { + if (surface_) + { + cairo_surface_destroy(surface_); + surface_ = NULL; + } + } + + + void CairoSurface::Allocate(unsigned int width, + unsigned int height, + bool hasAlpha) + { + Release(); + + hasAlpha_ = hasAlpha; + + surface_ = cairo_image_surface_create + (hasAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, width, height); + if (!surface_) + { + // Should never occur + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS) + { + LOG(ERROR) << "Cannot create a Cairo surface"; + cairo_surface_destroy(surface_); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + width_ = width; + height_ = height; + pitch_ = cairo_image_surface_get_stride(surface_); + buffer_ = cairo_image_surface_get_data(surface_); + } + + + CairoSurface::CairoSurface(Orthanc::ImageAccessor& accessor, + bool hasAlpha) : + hasAlpha_(hasAlpha) + { + if (accessor.GetFormat() != Orthanc::PixelFormat_BGRA32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + width_ = accessor.GetWidth(); + height_ = accessor.GetHeight(); + pitch_ = accessor.GetPitch(); + buffer_ = accessor.GetBuffer(); + + surface_ = cairo_image_surface_create_for_data + (reinterpret_cast<unsigned char*>(buffer_), + hasAlpha_ ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, + width_, height_, pitch_); + if (!surface_) + { + // Should never occur + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (cairo_surface_status(surface_) != CAIRO_STATUS_SUCCESS) + { + LOG(ERROR) << "Bad pitch for a Cairo surface"; + cairo_surface_destroy(surface_); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + void CairoSurface::SetSize(unsigned int width, + unsigned int height, + bool hasAlpha) + { + if (hasAlpha_ != hasAlpha || + width_ != width || + height_ != height) + { + Allocate(width, height, hasAlpha); + } + } + + + void CairoSurface::Copy(const CairoSurface& other) + { + SetSize(other.GetWidth(), other.GetHeight(), other.HasAlpha()); + + Orthanc::ImageAccessor source, target; + + other.GetReadOnlyAccessor(source); + GetWriteableAccessor(target); + + Orthanc::ImageProcessing::Copy(target, source); + + cairo_surface_mark_dirty(surface_); + } + + + void CairoSurface::Copy(const Orthanc::ImageAccessor& source, + bool hasAlpha) + { + SetSize(source.GetWidth(), source.GetHeight(), hasAlpha); + + Orthanc::ImageAccessor target; + GetWriteableAccessor(target); + + Orthanc::ImageProcessing::Convert(target, source); + + cairo_surface_mark_dirty(surface_); + } + + + void CairoSurface::GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const + { + target.AssignReadOnly(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_); + } + + + void CairoSurface::GetWriteableAccessor(Orthanc::ImageAccessor& target) + { + target.AssignWritable(Orthanc::PixelFormat_BGRA32, width_, height_, pitch_, buffer_); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Wrappers/CairoSurface.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,118 @@ +/** + * 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 <Core/Images/ImageAccessor.h> + +#include <boost/noncopyable.hpp> +#include <cairo.h> + +namespace OrthancStone +{ + class CairoSurface : public boost::noncopyable + { + private: + cairo_surface_t* surface_; + unsigned int width_; + unsigned int height_; + unsigned int pitch_; + void* buffer_; + bool hasAlpha_; + + void Release(); + + void Allocate(unsigned int width, + unsigned int height, + bool hasAlpha); + + public: + CairoSurface() : + surface_(NULL) + { + Allocate(0, 0, false); + } + + CairoSurface(unsigned int width, + unsigned int height, + bool hasAlpha) : + surface_(NULL) + { + Allocate(width, height, hasAlpha); + } + + CairoSurface(Orthanc::ImageAccessor& accessor, + bool hasAlpha); + + ~CairoSurface() + { + Release(); + } + + void SetSize(unsigned int width, + unsigned int height, + bool hasAlpha); + + void Copy(const CairoSurface& other); + + void Copy(const Orthanc::ImageAccessor& source, + bool hasAlpha); + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetPitch() const + { + return pitch_; + } + + const void* GetBuffer() const + { + return buffer_; + } + + void* GetBuffer() + { + return buffer_; + } + + cairo_surface_t* GetObject() + { + return surface_; + } + + bool HasAlpha() const + { + return hasAlpha_; + } + + void GetReadOnlyAccessor(Orthanc::ImageAccessor& target) const; + + void GetWriteableAccessor(Orthanc::ImageAccessor& target); + }; +}
--- a/Framework/dev.h Wed Jun 19 17:36:33 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,958 +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 "Layers/FrameRenderer.h" -#include "Layers/LineLayerRenderer.h" -#include "Layers/SliceOutlineRenderer.h" -#include "Toolbox/DownloadStack.h" -#include "Toolbox/GeometryToolbox.h" -#include "Toolbox/OrthancSlicesLoader.h" -#include "Volumes/ImageBuffer3D.h" -#include "Volumes/ISlicedVolume.h" -#include "Widgets/SliceViewerWidget.h" - -#include <Core/Logging.h> -#include <Core/Images/ImageProcessing.h> -#include <Core/OrthancException.h> - -#include <boost/math/special_functions/round.hpp> - - -namespace Deprecated -{ - // TODO: Handle errors while loading - class OrthancVolumeImage : - public ISlicedVolume, - public OrthancStone::IObserver - { - private: - OrthancSlicesLoader loader_; - std::auto_ptr<OrthancStone::ImageBuffer3D> image_; - std::auto_ptr<DownloadStack> downloadStack_; - bool computeRange_; - size_t pendingSlices_; - - void ScheduleSliceDownload() - { - assert(downloadStack_.get() != NULL); - - unsigned int slice; - if (downloadStack_->Pop(slice)) - { - loader_.ScheduleLoadSliceImage(slice, OrthancStone::SliceImageQuality_Jpeg90); - } - } - - - static bool IsCompatible(const Slice& a, - const Slice& b) - { - if (!OrthancStone::GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(), - b.GetGeometry().GetNormal())) - { - 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."; - return false; - } - - if (a.GetWidth() != b.GetWidth() || - a.GetHeight() != b.GetHeight()) - { - LOG(ERROR) << "The slices dimensions (width/height) are varying throughout the volume image"; - return false; - } - - if (!OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) || - !OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY())) - { - LOG(ERROR) << "The pixel spacing of the slices change across the volume image"; - return false; - } - - return true; - } - - - static double GetDistance(const Slice& a, - const Slice& b) - { - return fabs(a.GetGeometry().ProjectAlongNormal(a.GetGeometry().GetOrigin()) - - a.GetGeometry().ProjectAlongNormal(b.GetGeometry().GetOrigin())); - } - - - void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message) - { - assert(&message.GetOrigin() == &loader_); - - if (loader_.GetSlicesCount() == 0) - { - LOG(ERROR) << "Empty volume image"; - BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); - return; - } - - for (size_t i = 1; i < loader_.GetSlicesCount(); i++) - { - if (!IsCompatible(loader_.GetSlice(0), loader_.GetSlice(i))) - { - BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); - return; - } - } - - double spacingZ; - - if (loader_.GetSlicesCount() > 1) - { - spacingZ = GetDistance(loader_.GetSlice(0), loader_.GetSlice(1)); - } - else - { - // This is a volume with one single slice: Choose a dummy - // z-dimension for voxels - spacingZ = 1; - } - - for (size_t i = 1; i < loader_.GetSlicesCount(); i++) - { - if (!OrthancStone::LinearAlgebra::IsNear(spacingZ, GetDistance(loader_.GetSlice(i - 1), loader_.GetSlice(i)), - 0.001 /* this is expressed in mm */)) - { - LOG(ERROR) << "The distance between successive slices is not constant in a volume image"; - BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); - return; - } - } - - unsigned int width = loader_.GetSlice(0).GetWidth(); - unsigned int height = loader_.GetSlice(0).GetHeight(); - Orthanc::PixelFormat format = loader_.GetSlice(0).GetConverter().GetExpectedPixelFormat(); - LOG(INFO) << "Creating a volume image of size " << width << "x" << height - << "x" << loader_.GetSlicesCount() << " in " << Orthanc::EnumerationToString(format); - - image_.reset(new OrthancStone::ImageBuffer3D(format, width, height, static_cast<unsigned int>(loader_.GetSlicesCount()), computeRange_)); - image_->GetGeometry().SetAxialGeometry(loader_.GetSlice(0).GetGeometry()); - image_->GetGeometry().SetVoxelDimensions(loader_.GetSlice(0).GetPixelSpacingX(), - loader_.GetSlice(0).GetPixelSpacingY(), spacingZ); - image_->Clear(); - - downloadStack_.reset(new DownloadStack(static_cast<unsigned int>(loader_.GetSlicesCount()))); - pendingSlices_ = loader_.GetSlicesCount(); - - for (unsigned int i = 0; i < 4; i++) // Limit to 4 simultaneous downloads - { - ScheduleSliceDownload(); - } - - // TODO Check the DicomFrameConverter are constant - - BroadcastMessage(ISlicedVolume::GeometryReadyMessage(*this)); - } - - - void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message) - { - assert(&message.GetOrigin() == &loader_); - - LOG(ERROR) << "Unable to download a volume image"; - BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); - } - - - void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message) - { - assert(&message.GetOrigin() == &loader_); - - { - OrthancStone::ImageBuffer3D::SliceWriter writer(*image_, OrthancStone::VolumeProjection_Axial, message.GetSliceIndex()); - Orthanc::ImageProcessing::Copy(writer.GetAccessor(), message.GetImage()); - } - - BroadcastMessage(ISlicedVolume::SliceContentChangedMessage - (*this, message.GetSliceIndex(), message.GetSlice())); - - if (pendingSlices_ == 1) - { - BroadcastMessage(ISlicedVolume::VolumeReadyMessage(*this)); - pendingSlices_ = 0; - } - else if (pendingSlices_ > 1) - { - pendingSlices_ -= 1; - } - - ScheduleSliceDownload(); - } - - - void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message) - { - assert(&message.GetOrigin() == &loader_); - - LOG(ERROR) << "Cannot download slice " << message.GetSliceIndex() << " in a volume image"; - ScheduleSliceDownload(); - } - - - public: - OrthancVolumeImage(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc, - bool computeRange) : - ISlicedVolume(broker), - IObserver(broker), - loader_(broker, orthanc), - computeRange_(computeRange), - pendingSlices_(0) - { - loader_.RegisterObserverCallback( - new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryReadyMessage> - (*this, &OrthancVolumeImage::OnSliceGeometryReady)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryErrorMessage> - (*this, &OrthancVolumeImage::OnSliceGeometryError)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageReadyMessage> - (*this, &OrthancVolumeImage::OnSliceImageReady)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageErrorMessage> - (*this, &OrthancVolumeImage::OnSliceImageError)); - } - - void ScheduleLoadSeries(const std::string& seriesId) - { - loader_.ScheduleLoadSeries(seriesId); - } - - void ScheduleLoadInstance(const std::string& instanceId) - { - loader_.ScheduleLoadInstance(instanceId); - } - - void ScheduleLoadFrame(const std::string& instanceId, - unsigned int frame) - { - loader_.ScheduleLoadFrame(instanceId, frame); - } - - virtual size_t GetSlicesCount() const - { - return loader_.GetSlicesCount(); - } - - virtual const Slice& GetSlice(size_t index) const - { - return loader_.GetSlice(index); - } - - OrthancStone::ImageBuffer3D& GetImage() const - { - if (image_.get() == NULL) - { - // The geometry is not ready yet - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - return *image_; - } - } - - bool FitWindowingToRange(RenderStyle& style, - const DicomFrameConverter& converter) const - { - if (image_.get() == NULL) - { - return false; - } - else - { - return image_->FitWindowingToRange(style, converter); - } - } - }; - - - class VolumeImageGeometry - { - private: - unsigned int width_; - unsigned int height_; - size_t depth_; - double pixelSpacingX_; - double pixelSpacingY_; - double sliceThickness_; - OrthancStone::CoordinateSystem3D reference_; - DicomFrameConverter converter_; - - double ComputeAxialThickness(const OrthancVolumeImage& volume) const - { - double thickness; - - size_t n = volume.GetSlicesCount(); - if (n > 1) - { - const Slice& a = volume.GetSlice(0); - const Slice& b = volume.GetSlice(n - 1); - thickness = ((reference_.ProjectAlongNormal(b.GetGeometry().GetOrigin()) - - reference_.ProjectAlongNormal(a.GetGeometry().GetOrigin())) / - (static_cast<double>(n) - 1.0)); - } - else - { - thickness = volume.GetSlice(0).GetThickness(); - } - - if (thickness <= 0) - { - // The slices should have been sorted with increasing Z - // (along the normal) by the OrthancSlicesLoader - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - else - { - return thickness; - } - } - - void SetupAxial(const OrthancVolumeImage& volume) - { - const Slice& axial = volume.GetSlice(0); - - width_ = axial.GetWidth(); - height_ = axial.GetHeight(); - depth_ = volume.GetSlicesCount(); - - pixelSpacingX_ = axial.GetPixelSpacingX(); - pixelSpacingY_ = axial.GetPixelSpacingY(); - sliceThickness_ = ComputeAxialThickness(volume); - - reference_ = axial.GetGeometry(); - } - - void SetupCoronal(const OrthancVolumeImage& volume) - { - const Slice& axial = volume.GetSlice(0); - double axialThickness = ComputeAxialThickness(volume); - - width_ = axial.GetWidth(); - height_ = static_cast<unsigned int>(volume.GetSlicesCount()); - depth_ = axial.GetHeight(); - - pixelSpacingX_ = axial.GetPixelSpacingX(); - pixelSpacingY_ = axialThickness; - sliceThickness_ = axial.GetPixelSpacingY(); - - OrthancStone::Vector origin = axial.GetGeometry().GetOrigin(); - origin += (static_cast<double>(volume.GetSlicesCount() - 1) * - axialThickness * axial.GetGeometry().GetNormal()); - - reference_ = OrthancStone::CoordinateSystem3D(origin, - axial.GetGeometry().GetAxisX(), - - axial.GetGeometry().GetNormal()); - } - - void SetupSagittal(const OrthancVolumeImage& volume) - { - const Slice& axial = volume.GetSlice(0); - double axialThickness = ComputeAxialThickness(volume); - - width_ = axial.GetHeight(); - height_ = static_cast<unsigned int>(volume.GetSlicesCount()); - depth_ = axial.GetWidth(); - - pixelSpacingX_ = axial.GetPixelSpacingY(); - pixelSpacingY_ = axialThickness; - sliceThickness_ = axial.GetPixelSpacingX(); - - OrthancStone::Vector origin = axial.GetGeometry().GetOrigin(); - origin += (static_cast<double>(volume.GetSlicesCount() - 1) * - axialThickness * axial.GetGeometry().GetNormal()); - - reference_ = OrthancStone::CoordinateSystem3D(origin, - axial.GetGeometry().GetAxisY(), - axial.GetGeometry().GetNormal()); - } - - public: - VolumeImageGeometry(const OrthancVolumeImage& volume, - OrthancStone::VolumeProjection projection) - { - if (volume.GetSlicesCount() == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - converter_ = volume.GetSlice(0).GetConverter(); - - switch (projection) - { - case OrthancStone::VolumeProjection_Axial: - SetupAxial(volume); - break; - - case OrthancStone::VolumeProjection_Coronal: - SetupCoronal(volume); - break; - - case OrthancStone::VolumeProjection_Sagittal: - SetupSagittal(volume); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - size_t GetSlicesCount() const - { - return depth_; - } - - const OrthancStone::Vector& GetNormal() const - { - return reference_.GetNormal(); - } - - bool LookupSlice(size_t& index, - const OrthancStone::CoordinateSystem3D& slice) const - { - bool opposite; - if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, - reference_.GetNormal(), - slice.GetNormal())) - { - 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_)) - { - return false; - } - else - { - index = static_cast<size_t>(s); - return true; - } - } - - Slice* GetSlice(size_t slice) const - { - if (slice >= depth_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - OrthancStone::CoordinateSystem3D origin(reference_.GetOrigin() + - static_cast<double>(slice) * sliceThickness_ * reference_.GetNormal(), - reference_.GetAxisX(), - reference_.GetAxisY()); - - return new Slice(origin, pixelSpacingX_, pixelSpacingY_, sliceThickness_, - width_, height_, converter_); - } - } - }; - - - - class VolumeImageMPRSlicer : - public IVolumeSlicer, - public OrthancStone::IObserver - { - private: - class RendererFactory : public LayerReadyMessage::IRendererFactory - { - private: - const Orthanc::ImageAccessor& frame_; - const Slice& slice_; - bool isFullQuality_; - - public: - RendererFactory(const Orthanc::ImageAccessor& frame, - const Slice& slice, - bool isFullQuality) : - frame_(frame), - slice_(slice), - isFullQuality_(isFullQuality) - { - } - - virtual ILayerRenderer* CreateRenderer() const - { - return FrameRenderer::CreateRenderer(frame_, slice_, isFullQuality_); - } - }; - - - OrthancVolumeImage& volume_; - std::auto_ptr<VolumeImageGeometry> axialGeometry_; - 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_, OrthancStone::VolumeProjection_Axial)); - coronalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Coronal)); - sagittalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Sagittal)); - - BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); - } - - void OnGeometryError(const ISlicedVolume::GeometryErrorMessage& message) - { - assert(&message.GetOrigin() == &volume_); - - BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); - } - - void OnContentChanged(const ISlicedVolume::ContentChangedMessage& message) - { - assert(&message.GetOrigin() == &volume_); - - BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); - } - - void OnSliceContentChanged(const ISlicedVolume::SliceContentChangedMessage& message) - { - assert(&message.GetOrigin() == &volume_); - - //IVolumeSlicer::OnSliceContentChange(slice); - - // TODO Improve this? - BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); - } - - const VolumeImageGeometry& GetProjectionGeometry(OrthancStone::VolumeProjection projection) - { - if (!IsGeometryReady()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - switch (projection) - { - case OrthancStone::VolumeProjection_Axial: - return *axialGeometry_; - - case OrthancStone::VolumeProjection_Sagittal: - return *sagittalGeometry_; - - case OrthancStone::VolumeProjection_Coronal: - return *coronalGeometry_; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - bool DetectProjection(OrthancStone::VolumeProjection& projection, - const OrthancStone::CoordinateSystem3D& viewportSlice) - { - bool isOpposite; // Ignored - - if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, - viewportSlice.GetNormal(), - axialGeometry_->GetNormal())) - { - projection = OrthancStone::VolumeProjection_Axial; - return true; - } - else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, - viewportSlice.GetNormal(), - sagittalGeometry_->GetNormal())) - { - projection = OrthancStone::VolumeProjection_Sagittal; - return true; - } - else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, - viewportSlice.GetNormal(), - coronalGeometry_->GetNormal())) - { - projection = OrthancStone::VolumeProjection_Coronal; - return true; - } - else - { - return false; - } - } - - - public: - VolumeImageMPRSlicer(OrthancStone::MessageBroker& broker, - OrthancVolumeImage& volume) : - IVolumeSlicer(broker), - IObserver(broker), - volume_(volume) - { - volume_.RegisterObserverCallback( - new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryReadyMessage> - (*this, &VolumeImageMPRSlicer::OnGeometryReady)); - - volume_.RegisterObserverCallback( - new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryErrorMessage> - (*this, &VolumeImageMPRSlicer::OnGeometryError)); - - volume_.RegisterObserverCallback( - new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::ContentChangedMessage> - (*this, &VolumeImageMPRSlicer::OnContentChanged)); - - volume_.RegisterObserverCallback( - new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::SliceContentChangedMessage> - (*this, &VolumeImageMPRSlicer::OnSliceContentChanged)); - } - - virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, - const OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE - { - OrthancStone::VolumeProjection projection; - - if (!IsGeometryReady() || - !DetectProjection(projection, viewportSlice)) - { - return false; - } - else - { - // As the slices of the volumic image are arranged in a box, - // 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 OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE - { - OrthancStone::VolumeProjection projection; - - if (IsGeometryReady() && - DetectProjection(projection, viewportSlice)) - { - const VolumeImageGeometry& geometry = GetProjectionGeometry(projection); - - size_t closest; - - if (geometry.LookupSlice(closest, viewportSlice)) - { - bool isFullQuality = true; // TODO - - std::auto_ptr<Orthanc::Image> frame; - - { - OrthancStone::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())); - } - - std::auto_ptr<Slice> slice(geometry.GetSlice(closest)); - - RendererFactory factory(*frame, *slice, isFullQuality); - - BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice->GetGeometry())); - return; - } - } - - // Error - OrthancStone::CoordinateSystem3D slice; - BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, slice)); - } - }; - - - class VolumeImageInteractor : - public IWorldSceneInteractor, - public OrthancStone::IObserver - { - private: - SliceViewerWidget& widget_; - OrthancStone::VolumeProjection projection_; - std::auto_ptr<VolumeImageGeometry> slices_; - size_t slice_; - - protected: - void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message) - { - if (slices_.get() == NULL) - { - const OrthancVolumeImage& image = - dynamic_cast<const OrthancVolumeImage&>(message.GetOrigin()); - - slices_.reset(new VolumeImageGeometry(image, projection_)); - SetSlice(slices_->GetSlicesCount() / 2); - - widget_.FitContent(); - } - } - - virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, - const ViewportGeometry& view, - OrthancStone::MouseButton button, - OrthancStone::KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - IStatusBar* statusBar, - const std::vector<Touch>& touches) ORTHANC_OVERRIDE - { - return NULL; - } - - virtual void MouseOver(OrthancStone::CairoContext& context, - WorldSceneWidget& widget, - const ViewportGeometry& view, - double x, - double y, - IStatusBar* statusBar) ORTHANC_OVERRIDE - { - } - - virtual void MouseWheel(WorldSceneWidget& widget, - OrthancStone::MouseWheelDirection direction, - OrthancStone::KeyboardModifiers modifiers, - IStatusBar* statusBar) ORTHANC_OVERRIDE - { - int scale = (modifiers & OrthancStone::KeyboardModifiers_Control ? 10 : 1); - - switch (direction) - { - case OrthancStone::MouseWheelDirection_Up: - OffsetSlice(-scale); - break; - - case OrthancStone::MouseWheelDirection_Down: - OffsetSlice(scale); - break; - - default: - break; - } - } - - virtual void KeyPressed(WorldSceneWidget& widget, - OrthancStone::KeyboardKeys key, - char keyChar, - OrthancStone::KeyboardModifiers modifiers, - IStatusBar* statusBar) ORTHANC_OVERRIDE - { - switch (keyChar) - { - case 's': - widget.FitContent(); - break; - - default: - break; - } - } - - public: - VolumeImageInteractor(OrthancStone::MessageBroker& broker, - OrthancVolumeImage& volume, - SliceViewerWidget& widget, - OrthancStone::VolumeProjection projection) : - IObserver(broker), - widget_(widget), - projection_(projection) - { - widget.SetInteractor(*this); - - volume.RegisterObserverCallback( - new OrthancStone::Callable<VolumeImageInteractor, ISlicedVolume::GeometryReadyMessage> - (*this, &VolumeImageInteractor::OnGeometryReady)); - } - - bool IsGeometryReady() const - { - return slices_.get() != NULL; - } - - size_t GetSlicesCount() const - { - if (slices_.get() == NULL) - { - return 0; - } - else - { - return slices_->GetSlicesCount(); - } - } - - void OffsetSlice(int offset) - { - if (slices_.get() != NULL) - { - int slice = static_cast<int>(slice_) + offset; - - if (slice < 0) - { - slice = 0; - } - - if (slice >= static_cast<int>(slices_->GetSlicesCount())) - { - slice = static_cast<unsigned int>(slices_->GetSlicesCount()) - 1; - } - - if (slice != static_cast<int>(slice_)) - { - SetSlice(slice); - } - } - } - - void SetSlice(size_t slice) - { - if (slices_.get() != NULL) - { - slice_ = slice; - - std::auto_ptr<Slice> tmp(slices_->GetSlice(slice_)); - widget_.SetSlice(tmp->GetGeometry()); - } - } - }; - - - - class ReferenceLineSource : public IVolumeSlicer - { - private: - class RendererFactory : public LayerReadyMessage::IRendererFactory - { - private: - double x1_; - double y1_; - double x2_; - double y2_; - const OrthancStone::CoordinateSystem3D& slice_; - - public: - RendererFactory(double x1, - double y1, - double x2, - double y2, - const OrthancStone::CoordinateSystem3D& slice) : - x1_(x1), - y1_(y1), - x2_(x2), - y2_(y2), - slice_(slice) - { - } - - virtual ILayerRenderer* CreateRenderer() const - { - return new LineLayerRenderer(x1_, y1_, x2_, y2_, slice_); - } - }; - - SliceViewerWidget& otherPlane_; - - public: - ReferenceLineSource(OrthancStone::MessageBroker& broker, - SliceViewerWidget& otherPlane) : - IVolumeSlicer(broker), - otherPlane_(otherPlane) - { - BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); - } - - virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, - const OrthancStone::CoordinateSystem3D& viewportSlice) - { - return false; - } - - virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) - { - Slice reference(viewportSlice, 0.001); - - OrthancStone::Vector p, d; - - const OrthancStone::CoordinateSystem3D& slice = otherPlane_.GetSlice(); - - // Compute the line of intersection between the two slices - if (!OrthancStone::GeometryToolbox::IntersectTwoPlanes(p, d, - slice.GetOrigin(), slice.GetNormal(), - viewportSlice.GetOrigin(), viewportSlice.GetNormal())) - { - // The two slice are parallel, don't try and display the intersection - BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); - } - else - { - double x1, y1, x2, y2; - viewportSlice.ProjectPoint(x1, y1, p); - viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d); - - const OrthancStone::Extent2D extent = otherPlane_.GetSceneExtent(); - - if (OrthancStone::GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, - x1, y1, x2, y2, - extent.GetX1(), extent.GetY1(), - extent.GetX2(), extent.GetY2())) - { - RendererFactory factory(x1, y1, x2, y2, slice); - BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, reference.GetGeometry())); - } - else - { - // Error: Parallel slices - BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); - } - } - } - }; -}
--- a/Platforms/Generic/DelayedCallCommand.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Generic/DelayedCallCommand.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,7 +23,7 @@ #include "IOracleCommand.h" -#include "../../Framework/Toolbox/IDelayedCallExecutor.h" +#include "../../Framework/Deprecated/Toolbox/IDelayedCallExecutor.h" #include "../../Framework/Messages/IObservable.h" #include "../../Framework/Messages/ICallable.h" #include "../../Applications/Generic/NativeStoneApplicationContext.h"
--- a/Platforms/Generic/OracleDelayedCallExecutor.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Generic/OracleDelayedCallExecutor.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,7 +21,7 @@ #pragma once -#include "../../Framework/Toolbox/IDelayedCallExecutor.h" +#include "../../Framework/Deprecated/Toolbox/IDelayedCallExecutor.h" #include "Oracle.h" #include "../../Applications/Generic/NativeStoneApplicationContext.h" #include "DelayedCallCommand.h"
--- a/Platforms/Generic/OracleWebService.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Generic/OracleWebService.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -20,7 +20,7 @@ #include "OracleWebService.h" -#include "../../Framework/Toolbox/IWebService.h" +#include "../../Framework/Deprecated/Toolbox/IWebService.h" namespace Deprecated {
--- a/Platforms/Generic/OracleWebService.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Generic/OracleWebService.h Mon Jun 24 14:35:00 2019 +0200 @@ -21,7 +21,7 @@ #pragma once -#include "../../Framework/Toolbox/BaseWebService.h" +#include "../../Framework/Deprecated/Toolbox/BaseWebService.h" #include "Oracle.h" #include "WebServiceGetCommand.h" #include "WebServicePostCommand.h"
--- a/Platforms/Generic/WebServiceCommandBase.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Generic/WebServiceCommandBase.h Mon Jun 24 14:35:00 2019 +0200 @@ -23,7 +23,7 @@ #include "IOracleCommand.h" -#include "../../Framework/Toolbox/IWebService.h" +#include "../../Framework/Deprecated/Toolbox/IWebService.h" #include "../../Framework/Messages/IObservable.h" #include "../../Framework/Messages/ICallable.h" #include "../../Applications/Generic/NativeStoneApplicationContext.h"
--- a/Platforms/Wasm/Defaults.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Wasm/Defaults.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -2,9 +2,8 @@ #include "WasmWebService.h" #include "WasmDelayedCallExecutor.h" -#include <Framework/dev.h> -#include "Framework/Widgets/TestCairoWidget.h" -#include <Framework/Viewport/WidgetViewport.h> +#include "../../Framework/Deprecated/Widgets/TestCairoWidget.h" +#include <Framework/Deprecated/Viewport/WidgetViewport.h> #include <Applications/Wasm/StartupParametersBuilder.h> #include <Platforms/Wasm/WasmPlatformApplicationAdapter.h> #include <Core/Logging.h>
--- a/Platforms/Wasm/Defaults.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Wasm/Defaults.h Mon Jun 24 14:35:00 2019 +0200 @@ -2,9 +2,8 @@ #include <emscripten/emscripten.h> -#include <Framework/dev.h> -#include <Framework/Viewport/WidgetViewport.h> -#include <Framework/Widgets/LayoutWidget.h> +#include "../../Framework/Deprecated/Viewport/WidgetViewport.h" +#include "../../Framework/Deprecated/Widgets/LayoutWidget.h" #include <Applications/IStoneApplication.h> #include <Platforms/Wasm/WasmPlatformApplicationAdapter.h>
--- a/Platforms/Wasm/WasmDelayedCallExecutor.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Wasm/WasmDelayedCallExecutor.h Mon Jun 24 14:35:00 2019 +0200 @@ -1,6 +1,6 @@ #pragma once -#include <Framework/Toolbox/IDelayedCallExecutor.h> +#include "../../Framework/Deprecated/Toolbox/IDelayedCallExecutor.h" #include <Core/OrthancException.h> namespace Deprecated
--- a/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -1,6 +1,5 @@ #include "WasmPlatformApplicationAdapter.h" -#include "Framework/Toolbox/MessagingToolbox.h" #include "Framework/StoneException.h" #include <stdio.h> #include "Platforms/Wasm/Defaults.h" @@ -57,4 +56,4 @@ } } -} \ No newline at end of file +}
--- a/Platforms/Wasm/WasmViewport.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Wasm/WasmViewport.h Mon Jun 24 14:35:00 2019 +0200 @@ -1,6 +1,6 @@ #pragma once -#include <Framework/Viewport/WidgetViewport.h> +#include "../../Framework/Deprecated/Viewport/WidgetViewport.h" #include <emscripten/emscripten.h>
--- a/Platforms/Wasm/WasmWebService.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Platforms/Wasm/WasmWebService.h Mon Jun 24 14:35:00 2019 +0200 @@ -1,6 +1,6 @@ #pragma once -#include <Framework/Toolbox/BaseWebService.h> +#include "../../Framework/Deprecated/Toolbox/BaseWebService.h" #include <Core/OrthancException.h> namespace Deprecated
--- a/Resources/CMake/OrthancStoneConfiguration.cmake Wed Jun 19 17:36:33 2019 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Mon Jun 24 14:35:00 2019 +0200 @@ -32,7 +32,6 @@ 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 ##################################################################### @@ -66,6 +65,7 @@ message(FATAL_ERROR "WebAssembly target requires the emscripten compiler") endif() + set(ENABLE_THREADS OFF) add_definitions(-DORTHANC_ENABLE_WASM=1) else() if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR @@ -75,6 +75,7 @@ message(FATAL_ERROR "Trying to use a Web compiler for a native build") endif() + set(ENABLE_THREADS ON) add_definitions(-DORTHANC_ENABLE_WASM=0) endif() @@ -110,7 +111,6 @@ message("SDL is enabled") include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake) add_definitions( - -DORTHANC_ENABLE_NATIVE=1 -DORTHANC_ENABLE_QT=0 -DORTHANC_ENABLE_SDL=1 ) @@ -118,7 +118,6 @@ message("QT is enabled") include(${CMAKE_CURRENT_LIST_DIR}/QtConfiguration.cmake) add_definitions( - -DORTHANC_ENABLE_NATIVE=1 -DORTHANC_ENABLE_QT=1 -DORTHANC_ENABLE_SDL=0 ) @@ -128,11 +127,17 @@ add_definitions( -DORTHANC_ENABLE_SDL=0 -DORTHANC_ENABLE_QT=0 - -DORTHANC_ENABLE_NATIVE=0 ) endif() +if (ENABLE_THREADS) + add_definitions(-DORTHANC_ENABLE_THREADS=1) +else() + add_definitions(-DORTHANC_ENABLE_THREADS=0) +endif() + + if (ENABLE_OPENGL AND CMAKE_SYSTEM_NAME STREQUAL "Windows") include(${CMAKE_CURRENT_LIST_DIR}/GlewConfiguration.cmake) add_definitions( @@ -246,7 +251,6 @@ if (NOT ORTHANC_SANDBOXED) set(PLATFORM_SOURCES - ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoFont.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceCommandBase.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.cpp ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.cpp @@ -257,6 +261,12 @@ ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleDelayedCallExecutor.h ) + if (ENABLE_STONE_DEPRECATED) + list(APPEND PLATFORM_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/CairoFont.cpp + ) + endif() + if (ENABLE_SDL OR ENABLE_QT) list(APPEND APPLICATIONS_SOURCES ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationRunner.cpp @@ -304,63 +314,86 @@ DEPENDS "${ORTHANC_STONE_ROOT}/Platforms/Wasm/default-library.js") endif() +if (ENABLE_SDL OR ENABLE_WASM) + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Generic/GuiAdapter.cpp + ${ORTHANC_STONE_ROOT}/Applications/Generic/GuiAdapter.h + ) +endif() + +if (ENABLE_STONE_DEPRECATED) + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/CircleMeasureTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/ColorFrameRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/FrameRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/GrayscaleFrameRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/IVolumeSlicer.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/LineLayerRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/LineMeasureTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/RenderStyle.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/SliceOutlineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/SmartLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/BaseWebService.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DicomFrameConverter.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DownloadStack.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IWebService.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/MessagingToolbox.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancApiClient.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ParallelSlices.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ParallelSlicesCursor.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/Slice.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ViewportGeometry.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/IMouseTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/IStatusBar.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/IViewport.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/WidgetViewport.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Volumes/StructureSetLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/CairoWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/EmptyWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/IWidget.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/IWorldSceneInteractor.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/IWorldSceneMouseTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/LayoutWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/PanMouseTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/PanZoomMouseTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/SliceViewerWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/TestCairoWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/TestWorldSceneWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/WidgetBase.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/WorldSceneWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/ZoomMouseTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/dev.h + ) +endif() + + +if (ENABLE_THREADS) + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Messages/LockingEmitter.h + ${ORTHANC_STONE_ROOT}/Framework/Oracle/ThreadedOracle.cpp + ) +endif() + + +if (ENABLE_WASM) + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Oracle/WebAssemblyOracle.cpp + ) +endif() + + list(APPEND ORTHANC_STONE_SOURCES #${ORTHANC_STONE_ROOT}/Framework/Layers/SeriesFrameRendererFactory.cpp #${ORTHANC_STONE_ROOT}/Framework/Layers/SingleFrameRendererFactory.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/CairoCompositor.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ColorTextureSceneLayer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/FloatTextureSceneLayer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/InfoPanelSceneLayer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoTextRenderer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CompositorHelper.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/FixedPointAligner.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PanSceneTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PointerEvent.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PolylineSceneLayer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/RotateSceneTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Scene2D.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextSceneLayer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextureBaseSceneLayer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ZoomSceneTracker.cpp - - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureTracker.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureTracker.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditAngleMeasureTracker.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditCircleMeasureTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditCircleMeasureTracker.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureTracker.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/IFlexiblePointerTracker.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTools.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTools.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureToolsToolbox.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureToolsToolbox.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTrackers.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTrackers.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/PointerTypes.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.cpp - ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.h + ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp + ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp + ${ORTHANC_ROOT}/Plugins/Samples/Common/FullOrthancDataset.cpp + ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp ${ORTHANC_STONE_ROOT}/Framework/Fonts/FontRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Fonts/Glyph.cpp @@ -368,19 +401,23 @@ ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphBitmapAlphabet.cpp ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphTextureAlphabet.cpp ${ORTHANC_STONE_ROOT}/Framework/Fonts/TextBoundingBox.cpp - ${ORTHANC_STONE_ROOT}/Framework/Layers/CircleMeasureTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Layers/ColorFrameRenderer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Layers/DicomSeriesVolumeSlicer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Layers/DicomStructureSetSlicer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Layers/FrameRenderer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Layers/GrayscaleFrameRenderer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Layers/IVolumeSlicer.h - ${ORTHANC_STONE_ROOT}/Framework/Layers/LineLayerRenderer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Layers/LineMeasureTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Layers/RenderStyle.cpp - ${ORTHANC_STONE_ROOT}/Framework/Layers/SliceOutlineRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingItemsSorter.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingStrategy.cpp + ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderStateMachine.cpp + ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageForwarder.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/Promise.h + ${ORTHANC_STONE_ROOT}/Framework/Oracle/GetOrthancImageCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/OracleCommandWithPayload.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/OrthancRestApiCommand.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyAlphaLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyDicomLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp @@ -397,75 +434,94 @@ ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyTextLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyWidget.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyWindowingTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/SmartLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/CairoCompositor.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ColorTextureSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/FloatTextureSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/GrayscaleStyleConfigurator.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/InfoPanelSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoTextRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CompositorHelper.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/FixedPointAligner.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/LookupTableStyleConfigurator.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/LookupTableTextureSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PanSceneTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PointerEvent.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PolylineSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/RotateSceneTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Scene2D.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextureBaseSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ZoomSceneTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureCommand.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureCommand.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureCommand.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/IFlexiblePointerTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LayerHolder.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LayerHolder.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTool.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTool.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureToolsToolbox.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureToolsToolbox.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTrackers.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTrackers.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/PredeclaredTypes.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/UndoStack.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/UndoStack.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.h ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp ${ORTHANC_STONE_ROOT}/Framework/StoneException.h ${ORTHANC_STONE_ROOT}/Framework/StoneInitialization.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/AffineTransform2D.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/BaseWebService.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/CoordinateSystem3D.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomFrameConverter.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomInstanceParameters.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomStructureSet.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DownloadStack.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DynamicBitmap.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Extent2D.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/IDelayedCallExecutor.h - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/IWebService.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/LinearAlgebra.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/MessagingToolbox.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrientedBoundingBox.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancApiClient.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancSlicesLoader.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlices.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlicesCursor.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Slice.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/SlicesSorter.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ViewportGeometry.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/VolumeImageGeometry.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 + ${ORTHANC_STONE_ROOT}/Framework/Volumes/DicomVolumeImage.cpp + ${ORTHANC_STONE_ROOT}/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Volumes/DicomVolumeImageReslicer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Volumes/IVolumeSlicer.cpp ${ORTHANC_STONE_ROOT}/Framework/Volumes/ImageBuffer3D.cpp - ${ORTHANC_STONE_ROOT}/Framework/Volumes/StructureSetLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Volumes/OrientedVolumeBoundingBox.cpp + ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeImageGeometry.cpp ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeReslicer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Widgets/CairoWidget.cpp - ${ORTHANC_STONE_ROOT}/Framework/Widgets/EmptyWidget.cpp - ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWidget.h - ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneInteractor.h - ${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 - ${ORTHANC_STONE_ROOT}/Framework/Widgets/WidgetBase.cpp - ${ORTHANC_STONE_ROOT}/Framework/Widgets/WorldSceneWidget.cpp - ${ORTHANC_STONE_ROOT}/Framework/Widgets/ZoomMouseTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeSceneLayerSource.cpp + ${ORTHANC_STONE_ROOT}/Framework/Wrappers/CairoContext.cpp + ${ORTHANC_STONE_ROOT}/Framework/Wrappers/CairoSurface.cpp - ${ORTHANC_STONE_ROOT}/Framework/dev.h - - ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h - ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h - ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.cpp - ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h - ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h - ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageForwarder.cpp - ${ORTHANC_STONE_ROOT}/Framework/Messages/Promise.h - - ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomDatasetReader.cpp - ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp - ${ORTHANC_ROOT}/Plugins/Samples/Common/FullOrthancDataset.cpp - ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp - ${PLATFORM_SOURCES} ${APPLICATIONS_SOURCES} ${ORTHANC_CORE_SOURCES} @@ -487,21 +543,39 @@ if (ENABLE_OPENGL) list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Fonts/OpenGLTextCoordinates.h ${ORTHANC_STONE_ROOT}/Framework/Fonts/OpenGLTextCoordinates.cpp + ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLProgram.h ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLShader.h ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLShader.cpp + ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLTexture.h ${ORTHANC_STONE_ROOT}/Framework/OpenGL/OpenGLTexture.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/OpenGLCompositor.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/OpenGLCompositor.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureProgram.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLLinesProgram.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLLinesProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextProgram.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextureProgram.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextureProgram.cpp ) @@ -513,8 +587,6 @@ endif() -include_directories(${ORTHANC_STONE_ROOT}) - ## ## TEST - Automatically add all ".h" headers to the list of sources
--- a/Resources/CMake/OrthancStoneParameters.cmake Wed Jun 19 17:36:33 2019 +0200 +++ b/Resources/CMake/OrthancStoneParameters.cmake Mon Jun 24 14:35:00 2019 +0200 @@ -54,3 +54,4 @@ set(ENABLE_OPENGL ON CACHE INTERNAL "Enable support of OpenGL") set(ENABLE_WASM OFF CACHE INTERNAL "Enable support of WebAssembly") +set(ENABLE_STONE_DEPRECATED OFF CACHE INTERNAL "Enable backward compatibility with deprecated Stone classes")
--- a/Samples/Sdl/BasicScene.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Samples/Sdl/BasicScene.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -29,6 +29,7 @@ #include "../../Framework/Scene2D/Scene2D.h" #include "../../Framework/Scene2D/ZoomSceneTracker.h" #include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" #include "../../Framework/StoneInitialization.h" #include "../../Framework/Messages/MessageBroker.h" @@ -49,10 +50,9 @@ static const unsigned int FONT_SIZE = 32; static const int LAYER_POSITION = 150; -using namespace OrthancStone; - -void PrepareScene(ViewportControllerPtr controller) +void PrepareScene(boost::shared_ptr<OrthancStone::ViewportController> controller) { + using namespace OrthancStone; Scene2D& scene(*controller->GetScene()); // Texture of 2x2 size { @@ -104,21 +104,21 @@ { std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer); - layer->SetThickness(1); + layer->SetThickness(10); PolylineSceneLayer::Chain chain; chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5)); chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5)); chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5)); chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5)); - layer->AddChain(chain, true); + layer->AddChain(chain, true, 255, 0, 0); chain.clear(); chain.push_back(ScenePoint2D(-5, -5)); chain.push_back(ScenePoint2D(5, -5)); chain.push_back(ScenePoint2D(5, 5)); chain.push_back(ScenePoint2D(-5, 5)); - layer->AddChain(chain, true); + layer->AddChain(chain, true, 0, 255, 0); double dy = 1.01; chain.clear(); @@ -126,9 +126,8 @@ chain.push_back(ScenePoint2D(4, -4 + dy)); chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy)); chain.push_back(ScenePoint2D(4, 2)); - layer->AddChain(chain, false); + layer->AddChain(chain, false, 0, 0, 255); - layer->SetColor(0,255, 255); scene.SetLayer(50, layer.release()); } @@ -142,10 +141,11 @@ void TakeScreenshot(const std::string& target, - const Scene2D& scene, + const OrthancStone::Scene2D& scene, unsigned int canvasWidth, unsigned int canvasHeight) { + using namespace OrthancStone; // Take a screenshot, then save it as PNG file CairoCompositor compositor(scene, canvasWidth, canvasHeight); compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE, Orthanc::Encoding_Latin1); @@ -162,11 +162,12 @@ } -void HandleApplicationEvent(ViewportControllerPtr controller, - const OpenGLCompositor& compositor, +void HandleApplicationEvent(boost::shared_ptr<OrthancStone::ViewportController> controller, + const OrthancStone::OpenGLCompositor& compositor, const SDL_Event& event, - FlexiblePointerTrackerPtr& activeTracker) + boost::shared_ptr<OrthancStone::IFlexiblePointerTracker>& activeTracker) { + using namespace OrthancStone; Scene2D& scene(*controller->GetScene()); if (event.type == SDL_MOUSEMOTION) { @@ -276,8 +277,9 @@ } -void Run(ViewportControllerPtr controller) +void Run(boost::shared_ptr<OrthancStone::ViewportController> controller) { + using namespace OrthancStone; SdlOpenGLWindow window("Hello", 1024, 768); controller->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); @@ -289,7 +291,7 @@ compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE, Orthanc::Encoding_Latin1); - FlexiblePointerTrackerPtr tracker; + boost::shared_ptr<IFlexiblePointerTracker> tracker; bool stop = false; while (!stop) @@ -368,14 +370,16 @@ **/ int main(int argc, char* argv[]) { + using namespace OrthancStone; StoneInitialize(); Orthanc::Logging::EnableInfoLevel(true); try { MessageBroker broker; - ViewportControllerPtr controller = boost::make_shared<ViewportController>( - boost::ref(broker)); + boost::shared_ptr<UndoStack> undoStack(new UndoStack); + boost::shared_ptr<ViewportController> controller = boost::make_shared<ViewportController>( + undoStack, boost::ref(broker)); PrepareScene(controller); Run(controller); }
--- a/Samples/Sdl/CMakeLists.txt Wed Jun 19 17:36:33 2019 +0200 +++ b/Samples/Sdl/CMakeLists.txt Mon Jun 24 14:35:00 2019 +0200 @@ -47,6 +47,11 @@ include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + + ##################################################################### ## Build the samples ##################################################################### @@ -55,19 +60,19 @@ ${ORTHANC_STONE_SOURCES} ) +# +# BasicScene +# + add_executable(BasicScene BasicScene.cpp ) target_link_libraries(BasicScene OrthancStone) -if(ENABLE_SDL_CONSOLE) - add_definitions( - -DENABLE_SDL_CONSOLE=1 - ) - LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.c") - LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.h") -endif() +# +# BasicScene +# LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp") LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.cpp") @@ -83,8 +88,23 @@ target_link_libraries(TrackerSample OrthancStone) +# +# Loader +# + add_executable(Loader Loader.cpp ) target_link_libraries(Loader OrthancStone) + +# +# FusionMprSdl +# + +add_executable(FusionMprSdl + FusionMprSdl.cpp + FusionMprSdl.h +) + +target_link_libraries(FusionMprSdl OrthancStone)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/FusionMprSdl.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,813 @@ +/** + * 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 "FusionMprSdl.h" + +#include "../../Applications/Sdl/SdlOpenGLWindow.h" + +#include "../../Framework/StoneInitialization.h" + +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" + +#include "../../Framework/Scene2DViewport/UndoStack.h" +#include "../../Framework/Scene2DViewport/CreateLineMeasureTracker.h" +#include "../../Framework/Scene2DViewport/CreateAngleMeasureTracker.h" +#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" +#include "../../Framework/Scene2DViewport/MeasureTool.h" +#include "../../Framework/Scene2DViewport/PredeclaredTypes.h" + +#include "../../Framework/Volumes/VolumeSceneLayerSource.h" + +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Images/PngWriter.h> +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/make_shared.hpp> + +#include <stdio.h> +#include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../../Framework/Oracle/ThreadedOracle.h" +#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" +#include "../../Framework/Loaders/OrthancMultiframeVolumeLoader.h" +#include "../../Framework/Loaders/DicomStructureSetLoader.h" +#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" +#include "../../Framework/Scene2D/LookupTableStyleConfigurator.h" +#include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h" +#include "Core/SystemToolbox.h" + +namespace OrthancStone +{ + const char* FusionMprMeasureToolToString(size_t i) + { + static const char* descs[] = { + "FusionMprGuiTool_Rotate", + "FusionMprGuiTool_Pan", + "FusionMprGuiTool_Zoom", + "FusionMprGuiTool_LineMeasure", + "FusionMprGuiTool_CircleMeasure", + "FusionMprGuiTool_AngleMeasure", + "FusionMprGuiTool_EllipseMeasure", + "FusionMprGuiTool_LAST" + }; + if (i >= FusionMprGuiTool_LAST) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong tool index"); + } + return descs[i]; + } + + boost::shared_ptr<Scene2D> FusionMprSdlApp::GetScene() + { + return controller_->GetScene(); + } + + boost::shared_ptr<const Scene2D> FusionMprSdlApp::GetScene() const + { + return controller_->GetScene(); + } + + void FusionMprSdlApp::SelectNextTool() + { + currentTool_ = static_cast<FusionMprGuiTool>(currentTool_ + 1); + if (currentTool_ == FusionMprGuiTool_LAST) + currentTool_ = static_cast<FusionMprGuiTool>(0);; + printf("Current tool is now: %s\n", FusionMprMeasureToolToString(currentTool_)); + } + + void FusionMprSdlApp::DisplayInfoText() + { + // do not try to use stuff too early! + if (compositor_.get() == NULL) + return; + + std::stringstream msg; + + for (std::map<std::string, std::string>::const_iterator kv = infoTextMap_.begin(); + kv != infoTextMap_.end(); ++kv) + { + msg << kv->first << " : " << kv->second << std::endl; + } + std::string msgS = msg.str(); + + TextSceneLayer* layerP = NULL; + if (GetScene()->HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = dynamic_cast<TextSceneLayer&>( + GetScene()->GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); + layerP = &layer; + } + else + { + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer); + layerP = layer.get(); + layer->SetColor(0, 255, 0); + layer->SetFontIndex(1); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_TopLeft); + //layer->SetPosition(0,0); + GetScene()->SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release()); + } + // position the fixed info text in the upper right corner + layerP->SetText(msgS.c_str()); + double cX = compositor_->GetCanvasWidth() * (-0.5); + double cY = compositor_->GetCanvasHeight() * (-0.5); + GetScene()->GetCanvasToSceneTransform().Apply(cX,cY); + layerP->SetPosition(cX, cY); + } + + void FusionMprSdlApp::DisplayFloatingCtrlInfoText(const PointerEvent& e) + { + ScenePoint2D p = e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform()); + + char buf[128]; + sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)", + p.GetX(), p.GetY(), + e.GetMainPosition().GetX(), e.GetMainPosition().GetY()); + + if (GetScene()->HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = + dynamic_cast<TextSceneLayer&>(GetScene()->GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer); + layer->SetColor(0, 255, 0); + layer->SetText(buf); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_BottomCenter); + layer->SetPosition(p.GetX(), p.GetY()); + GetScene()->SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release()); + } + } + + void FusionMprSdlApp::HideInfoText() + { + GetScene()->DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX); + } + + void FusionMprSdlApp::HandleApplicationEvent( + const SDL_Event & event) + { + DisplayInfoText(); + + if (event.type == SDL_MOUSEMOTION) + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + if (activeTracker_.get() == NULL && + SDL_SCANCODE_LALT < scancodeCount && + keyboardState[SDL_SCANCODE_LALT]) + { + // The "left-ctrl" key is down, while no tracker is present + // Let's display the info text + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates( + event.button.x, event.button.y)); + + DisplayFloatingCtrlInfoText(e); + } + else + { + HideInfoText(); + //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)"; + if (activeTracker_.get() != NULL) + { + //LOG(TRACE) << "(activeTracker_.get() != NULL)"; + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates( + event.button.x, event.button.y)); + + //LOG(TRACE) << "event.button.x = " << event.button.x << " " << + // "event.button.y = " << event.button.y; + LOG(TRACE) << "activeTracker_->PointerMove(e); " << + e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY(); + + activeTracker_->PointerMove(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + } + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + if (activeTracker_) + { + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates(event.button.x, event.button.y)); + activeTracker_->PointerUp(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates( + event.button.x, event.button.y)); + if (activeTracker_) + { + activeTracker_->PointerDown(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + else + { + // we ATTEMPT to create a tracker if need be + activeTracker_ = CreateSuitableTracker(event, e); + } + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_ESCAPE: + if (activeTracker_) + { + activeTracker_->Cancel(); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + break; + + case SDLK_t: + if (!activeTracker_) + SelectNextTool(); + else + { + LOG(WARNING) << "You cannot change the active tool when an interaction" + " is taking place"; + } + break; + case SDLK_s: + controller_->FitContent(compositor_->GetCanvasWidth(), + compositor_->GetCanvasHeight()); + break; + + case SDLK_z: + LOG(TRACE) << "SDLK_z has been pressed. event.key.keysym.mod == " << event.key.keysym.mod; + if (event.key.keysym.mod & KMOD_CTRL) + { + if (controller_->CanUndo()) + { + LOG(TRACE) << "Undoing..."; + controller_->Undo(); + } + else + { + LOG(WARNING) << "Nothing to undo!!!"; + } + } + break; + + case SDLK_y: + LOG(TRACE) << "SDLK_y has been pressed. event.key.keysym.mod == " << event.key.keysym.mod; + if (event.key.keysym.mod & KMOD_CTRL) + { + if (controller_->CanRedo()) + { + LOG(TRACE) << "Redoing..."; + controller_->Redo(); + } + else + { + LOG(WARNING) << "Nothing to redo!!!"; + } + } + break; + + case SDLK_c: + TakeScreenshot( + "screenshot.png", + compositor_->GetCanvasWidth(), + compositor_->GetCanvasHeight()); + break; + + default: + break; + } + } + } + + + void FusionMprSdlApp::OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message) + { + DisplayInfoText(); + } + + boost::shared_ptr<IFlexiblePointerTracker> FusionMprSdlApp::CreateSuitableTracker( + const SDL_Event & event, + const PointerEvent & e) + { + using namespace Orthanc; + + switch (event.button.button) + { + case SDL_BUTTON_MIDDLE: + return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker + (controller_, e)); + + case SDL_BUTTON_RIGHT: + return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker + (controller_, e, compositor_->GetCanvasHeight())); + + case SDL_BUTTON_LEFT: + { + //LOG(TRACE) << "CreateSuitableTracker: case SDL_BUTTON_LEFT:"; + // TODO: we need to iterate on the set of measuring tool and perform + // a hit test to check if a tracker needs to be created for edition. + // Otherwise, depending upon the active tool, we might want to create + // a "measuring tool creation" tracker + + // TODO: if there are conflicts, we should prefer a tracker that + // pertains to the type of measuring tool currently selected (TBD?) + boost::shared_ptr<IFlexiblePointerTracker> hitTestTracker = TrackerHitTest(e); + + if (hitTestTracker != NULL) + { + //LOG(TRACE) << "hitTestTracker != NULL"; + return hitTestTracker; + } + else + { + switch (currentTool_) + { + case FusionMprGuiTool_Rotate: + //LOG(TRACE) << "Creating RotateSceneTracker"; + return boost::shared_ptr<IFlexiblePointerTracker>(new RotateSceneTracker( + controller_, e)); + case FusionMprGuiTool_Pan: + return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker( + controller_, e)); + case FusionMprGuiTool_Zoom: + return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker( + controller_, e, compositor_->GetCanvasHeight())); + //case GuiTool_AngleMeasure: + // return new AngleMeasureTracker(GetScene(), e); + //case GuiTool_CircleMeasure: + // return new CircleMeasureTracker(GetScene(), e); + //case GuiTool_EllipseMeasure: + // return new EllipseMeasureTracker(GetScene(), e); + case FusionMprGuiTool_LineMeasure: + return boost::shared_ptr<IFlexiblePointerTracker>(new CreateLineMeasureTracker( + IObserver::GetBroker(), controller_, e)); + case FusionMprGuiTool_AngleMeasure: + return boost::shared_ptr<IFlexiblePointerTracker>(new CreateAngleMeasureTracker( + IObserver::GetBroker(), controller_, e)); + case FusionMprGuiTool_CircleMeasure: + LOG(ERROR) << "Not implemented yet!"; + return boost::shared_ptr<IFlexiblePointerTracker>(); + case FusionMprGuiTool_EllipseMeasure: + LOG(ERROR) << "Not implemented yet!"; + return boost::shared_ptr<IFlexiblePointerTracker>(); + default: + throw OrthancException(ErrorCode_InternalError, "Wrong tool!"); + } + } + } + default: + return boost::shared_ptr<IFlexiblePointerTracker>(); + } + } + + + FusionMprSdlApp::FusionMprSdlApp(MessageBroker& broker) + : IObserver(broker) + , broker_(broker) + , oracleObservable_(broker) + , oracle_(*this) + , currentTool_(FusionMprGuiTool_Rotate) + , undoStack_(new UndoStack) + { + //oracleObservable.RegisterObserverCallback + //(new Callable + // <FusionMprSdlApp, SleepOracleCommand::TimeoutMessage>(*this, &FusionMprSdlApp::Handle)); + + //oracleObservable.RegisterObserverCallback + //(new Callable + // <Toto, GetOrthancImageCommand::SuccessMessage>(*this, &FusionMprSdlApp::Handle)); + + //oracleObservable.RegisterObserverCallback + //(new Callable + // <FusionMprSdlApp, GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &ToFusionMprSdlAppto::Handle)); + + oracleObservable_.RegisterObserverCallback + (new Callable + <FusionMprSdlApp, OracleCommandExceptionMessage>(*this, &FusionMprSdlApp::Handle)); + + controller_ = boost::shared_ptr<ViewportController>( + new ViewportController(undoStack_, broker_)); + + controller_->RegisterObserverCallback( + new Callable<FusionMprSdlApp, ViewportController::SceneTransformChanged> + (*this, &FusionMprSdlApp::OnSceneTransformChanged)); + + TEXTURE_2x2_1_ZINDEX = 1; + TEXTURE_1x1_ZINDEX = 2; + TEXTURE_2x2_2_ZINDEX = 3; + LINESET_1_ZINDEX = 4; + LINESET_2_ZINDEX = 5; + FLOATING_INFOTEXT_LAYER_ZINDEX = 6; + FIXED_INFOTEXT_LAYER_ZINDEX = 7; + } + + void FusionMprSdlApp::PrepareScene() + { + // Texture of 2x2 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t* p = reinterpret_cast<uint8_t*>(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast<uint8_t*>(i.GetRow(1)); + p[0] = 0; + p[1] = 0; + p[2] = 255; + + p[3] = 255; + p[4] = 0; + p[5] = 0; + + GetScene()->SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i)); + } + } + + void FusionMprSdlApp::DisableTracker() + { + if (activeTracker_) + { + activeTracker_->Cancel(); + activeTracker_.reset(); + } + } + + void FusionMprSdlApp::TakeScreenshot(const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight) + { + CairoCompositor compositor(*GetScene(), canvasWidth, canvasHeight); + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE_0, Orthanc::Encoding_Latin1); + compositor.Refresh(); + + Orthanc::ImageAccessor canvas; + compositor.GetCanvas().GetReadOnlyAccessor(canvas); + + Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); + Orthanc::ImageProcessing::Convert(png, canvas); + + Orthanc::PngWriter writer; + writer.WriteToFile(target, png); + } + + + boost::shared_ptr<IFlexiblePointerTracker> FusionMprSdlApp::TrackerHitTest(const PointerEvent & e) + { + // std::vector<boost::shared_ptr<MeasureTool>> measureTools_; + return boost::shared_ptr<IFlexiblePointerTracker>(); + } + + static void GLAPIENTRY + OpenGLMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) + { + if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) + { + fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), + type, severity, message); + } + } + + static bool g_stopApplication = false; + + + void FusionMprSdlApp::Handle(const DicomVolumeImage::GeometryReadyMessage& message) + { + printf("Geometry ready\n"); + + //plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry(); + //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry(); + plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry(); + plane_.SetOrigin(message.GetOrigin().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f)); + + //Refresh(); + } + + + void FusionMprSdlApp::Handle(const OracleCommandExceptionMessage& message) + { + printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType()); + + switch (message.GetCommand().GetType()) + { + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + printf("URI: [%s]\n", dynamic_cast<const GetOrthancWebViewerJpegCommand&> + (message.GetCommand()).GetUri().c_str()); + break; + + default: + break; + } + } + + void FusionMprSdlApp::SetVolume1(int depth, + const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + source1_.reset(new OrthancStone::VolumeSceneLayerSource(*controller_->GetScene(), depth, volume)); + + if (style != NULL) + { + source1_->SetConfigurator(style); + } + } + + void FusionMprSdlApp::SetVolume2(int depth, + const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + source2_.reset(new OrthancStone::VolumeSceneLayerSource(*controller_->GetScene(), depth, volume)); + + if (style != NULL) + { + source2_->SetConfigurator(style); + } + } + + void FusionMprSdlApp::SetStructureSet(int depth, + const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume) + { + source3_.reset(new OrthancStone::VolumeSceneLayerSource(*controller_->GetScene(), depth, volume)); + } + + void FusionMprSdlApp::Run() + { + // False means we do NOT let Windows treat this as a legacy application + // that needs to be scaled + SdlOpenGLWindow window("Hello", 1024, 1024, false); + + controller_->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + compositor_.reset(new OpenGLCompositor(window, *GetScene())); + + compositor_->SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_0, Orthanc::Encoding_Latin1); + compositor_->SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_1, Orthanc::Encoding_Latin1); + + + //////// from loader + { + Orthanc::WebServiceParameters p; + //p.SetUrl("http://localhost:8043/"); + p.SetCredentials("orthanc", "orthanc"); + oracle_.SetOrthancParameters(p); + } + + //////// from Run + + boost::shared_ptr<DicomVolumeImage> ct(new DicomVolumeImage); + boost::shared_ptr<DicomVolumeImage> dose(new DicomVolumeImage); + + + boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader; + boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader; + boost::shared_ptr<DicomStructureSetLoader> rtstructLoader; + + { + ctLoader.reset(new OrthancSeriesVolumeProgressiveLoader(ct, oracle_, oracleObservable_)); + doseLoader.reset(new OrthancMultiframeVolumeLoader(dose, oracle_, oracleObservable_)); + rtstructLoader.reset(new DicomStructureSetLoader(oracle_, oracleObservable_)); + } + + //toto->SetReferenceLoader(*ctLoader); + //doseLoader->RegisterObserverCallback + //(new Callable + // <FusionMprSdlApp, DicomVolumeImage::GeometryReadyMessage>(*this, &FusionMprSdlApp::Handle)); + ctLoader->RegisterObserverCallback + (new Callable + <FusionMprSdlApp, DicomVolumeImage::GeometryReadyMessage>(*this, &FusionMprSdlApp::Handle)); + + this->SetVolume1(0, ctLoader, new GrayscaleStyleConfigurator); + + { + std::auto_ptr<LookupTableStyleConfigurator> config(new LookupTableStyleConfigurator); + config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); + + boost::shared_ptr<DicomVolumeImageMPRSlicer> tmp(new DicomVolumeImageMPRSlicer(dose)); + this->SetVolume2(1, tmp, config.release()); + } + + this->SetStructureSet(2, rtstructLoader); + +#if 1 + /* + BGO data + http://localhost:8042/twiga-orthanc-viewer-demo/twiga-orthanc-viewer-demo.html?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa + & + dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb + & + struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 + */ + ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT + doseLoader->LoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // RT-DOSE + rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT +#else + //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT + //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE + //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6"); // RT-STRUCT + + // 2017-05-16 + ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT + doseLoader->LoadInstance("eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad"); // RT-DOSE + rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT +#endif + + oracle_.Start(); + +//// END from loader + + while (!g_stopApplication) + { + compositor_->Refresh(); + +//////// from loader + if (source1_.get() != NULL) + { + source1_->Update(plane_); + } + + if (source2_.get() != NULL) + { + source2_->Update(plane_); + } + + if (source3_.get() != NULL) + { + source3_->Update(plane_); + } +//// END from loader + + SDL_Event event; + while (!g_stopApplication && SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + g_stopApplication = true; + break; + } + else if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + DisableTracker(); // was: tracker.reset(NULL); + compositor_->UpdateSize(); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + window.GetWindow().ToggleMaximize(); + break; + + case SDLK_s: + controller_->FitContent( + window.GetCanvasWidth(), window.GetCanvasHeight()); + break; + + case SDLK_q: + g_stopApplication = true; + break; + default: + break; + } + } + HandleApplicationEvent(event); + } + SDL_Delay(1); + } + + // the following is paramount because the compositor holds a reference + // to the scene and we do not want this reference to become dangling + compositor_.reset(NULL); + + //// from loader + + //Orthanc::SystemToolbox::ServerBarrier(); + + /** + * WARNING => The oracle must be stopped BEFORE the objects using + * it are destroyed!!! This forces to wait for the completion of + * the running callback methods. Otherwise, the callbacks methods + * might still be running while their parent object is destroyed, + * resulting in crashes. This is very visible if adding a sleep(), + * as in (*). + **/ + + oracle_.Stop(); + //// END from loader + } + + void FusionMprSdlApp::SetInfoDisplayMessage( + std::string key, std::string value) + { + if (value == "") + infoTextMap_.erase(key); + else + infoTextMap_[key] = value; + DisplayInfoText(); + } + +} + + +boost::weak_ptr<OrthancStone::FusionMprSdlApp> g_app; + +void FusionMprSdl_SetInfoDisplayMessage(std::string key, std::string value) +{ + boost::shared_ptr<OrthancStone::FusionMprSdlApp> app = g_app.lock(); + if (app) + { + app->SetInfoDisplayMessage(key, value); + } +} + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + using namespace OrthancStone; + + StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); +// Orthanc::Logging::EnableTraceLevel(true); + + try + { + OrthancStone::MessageBroker broker; + boost::shared_ptr<FusionMprSdlApp> app(new FusionMprSdlApp(broker)); + g_app = app; + app->PrepareScene(); + app->Run(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/FusionMprSdl.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,205 @@ +/** + * 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 "../../Framework/Messages/IObserver.h" +#include "../../Framework/Messages/IMessageEmitter.h" +#include "../../Framework/Oracle/OracleCommandExceptionMessage.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/Volumes/DicomVolumeImage.h" +#include "../../Framework/Oracle/ThreadedOracle.h" + +#include <boost/enable_shared_from_this.hpp> +#include <boost/thread.hpp> +#include <boost/noncopyable.hpp> + +#include <SDL.h> + +namespace OrthancStone +{ + class OpenGLCompositor; + class IVolumeSlicer; + class ILayerStyleConfigurator; + class DicomStructureSetLoader; + class IOracle; + class ThreadedOracle; + class VolumeSceneLayerSource; + class NativeFusionMprApplicationContext; + + + enum FusionMprGuiTool + { + FusionMprGuiTool_Rotate = 0, + FusionMprGuiTool_Pan, + FusionMprGuiTool_Zoom, + FusionMprGuiTool_LineMeasure, + FusionMprGuiTool_CircleMeasure, + FusionMprGuiTool_AngleMeasure, + FusionMprGuiTool_EllipseMeasure, + FusionMprGuiTool_LAST + }; + + const char* MeasureToolToString(size_t i); + + static const unsigned int FONT_SIZE_0 = 32; + static const unsigned int FONT_SIZE_1 = 24; + + class Scene2D; + class UndoStack; + + /** + This application subclasses IMessageEmitter to use a mutex before forwarding Oracle messages (that + can be sent from multiple threads) + */ + class FusionMprSdlApp : public IObserver + , public boost::enable_shared_from_this<FusionMprSdlApp> + , public IMessageEmitter + { + public: + // 12 because. + FusionMprSdlApp(MessageBroker& broker); + + void PrepareScene(); + void Run(); + void SetInfoDisplayMessage(std::string key, std::string value); + void DisableTracker(); + + boost::shared_ptr<Scene2D> GetScene(); + boost::shared_ptr<const Scene2D> GetScene() const; + + void HandleApplicationEvent(const SDL_Event& event); + + /** + This method is called when the scene transform changes. It allows to + recompute the visual elements whose content depend upon the scene transform + */ + void OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message); + + + virtual void EmitMessage(const IObserver& observer, + const IMessage& message) ORTHANC_OVERRIDE + { + try + { + boost::unique_lock<boost::shared_mutex> lock(mutex_); + oracleObservable_.EmitMessage(observer, message); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while emitting a message: " << e.What(); + throw; + } + } + + private: +#if 1 + // if threaded (not wasm) + MessageBroker& broker_; + IObservable oracleObservable_; + ThreadedOracle oracle_; + boost::shared_mutex mutex_; // to serialize messages from the ThreadedOracle +#endif + + void SelectNextTool(); + + /** + This returns a random point in the canvas part of the scene, but in + scene coordinates + */ + ScenePoint2D GetRandomPointInScene() const; + + boost::shared_ptr<IFlexiblePointerTracker> TrackerHitTest(const PointerEvent& e); + + boost::shared_ptr<IFlexiblePointerTracker> CreateSuitableTracker( + const SDL_Event& event, + const PointerEvent& e); + + void TakeScreenshot( + const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight); + + /** + This adds the command at the top of the undo stack + */ + void Commit(boost::shared_ptr<TrackerCommand> cmd); + void Undo(); + void Redo(); + + + // TODO private + void Handle(const DicomVolumeImage::GeometryReadyMessage& message); + void Handle(const OracleCommandExceptionMessage& message); + + void SetVolume1( + int depth, + const boost::shared_ptr<IVolumeSlicer>& volume, + ILayerStyleConfigurator* style); + + void SetVolume2( + int depth, + const boost::shared_ptr<IVolumeSlicer>& volume, + ILayerStyleConfigurator* style); + + void SetStructureSet( + int depth, + const boost::shared_ptr<DicomStructureSetLoader>& volume); + + + + private: + void DisplayFloatingCtrlInfoText(const PointerEvent& e); + void DisplayInfoText(); + void HideInfoText(); + + private: + CoordinateSystem3D plane_; + + boost::shared_ptr<VolumeSceneLayerSource> source1_, source2_, source3_; + + std::auto_ptr<OpenGLCompositor> compositor_; + /** + WARNING: the measuring tools do store a reference to the scene, and it + paramount that the scene gets destroyed AFTER the measurement tools. + */ + boost::shared_ptr<ViewportController> controller_; + + std::map<std::string, std::string> infoTextMap_; + boost::shared_ptr<IFlexiblePointerTracker> activeTracker_; + + //static const int LAYER_POSITION = 150; + + int TEXTURE_2x2_1_ZINDEX; + int TEXTURE_1x1_ZINDEX; + int TEXTURE_2x2_2_ZINDEX; + int LINESET_1_ZINDEX; + int LINESET_2_ZINDEX; + int FLOATING_INFOTEXT_LAYER_ZINDEX; + int FIXED_INFOTEXT_LAYER_ZINDEX; + + FusionMprGuiTool currentTool_; + boost::shared_ptr<UndoStack> undoStack_; + + }; + +} + + + \ No newline at end of file
--- a/Samples/Sdl/Loader.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Samples/Sdl/Loader.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -18,2245 +18,36 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ -// From Stone -#include "../../Framework/Loaders/BasicFetchingItemsSorter.h" -#include "../../Framework/Loaders/BasicFetchingStrategy.h" -#include "../../Framework/Messages/ICallable.h" -#include "../../Framework/Messages/IMessage.h" -#include "../../Framework/Messages/IObservable.h" -#include "../../Framework/Messages/MessageBroker.h" -#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" -#include "../../Framework/Scene2D/FloatTextureSceneLayer.h" -#include "../../Framework/Scene2D/Scene2D.h" + +#include "../../Framework/Loaders/DicomStructureSetLoader.h" +#include "../../Framework/Loaders/OrthancMultiframeVolumeLoader.h" +#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" +#include "../../Framework/Oracle/SleepOracleCommand.h" +#include "../../Framework/Oracle/ThreadedOracle.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" +#include "../../Framework/Scene2D/LookupTableStyleConfigurator.h" #include "../../Framework/StoneInitialization.h" -#include "../../Framework/Toolbox/GeometryToolbox.h" -#include "../../Framework/Toolbox/SlicesSorter.h" -#include "../../Framework/Volumes/ImageBuffer3D.h" +#include "../../Framework/Volumes/VolumeSceneLayerSource.h" +#include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h" +#include "../../Framework/Volumes/DicomVolumeImageReslicer.h" // From Orthanc framework -#include <Core/Compression/GzipCompressor.h> -#include <Core/Compression/ZlibCompressor.h> -#include <Core/DicomFormat/DicomArray.h> -#include <Core/DicomFormat/DicomImageInformation.h> -#include <Core/HttpClient.h> -#include <Core/IDynamicObject.h> -#include <Core/Images/Image.h> #include <Core/Images/ImageProcessing.h> -#include <Core/Images/JpegReader.h> -#include <Core/Images/PamReader.h> -#include <Core/Images/PngReader.h> #include <Core/Images/PngWriter.h> #include <Core/Logging.h> -#include <Core/MultiThreading/SharedMessageQueue.h> #include <Core/OrthancException.h> #include <Core/SystemToolbox.h> -#include <Core/Toolbox.h> - -#include <json/reader.h> -#include <json/value.h> -#include <json/writer.h> - -#include <list> -#include <stdio.h> - - - -namespace Refactoring -{ - class IOracleCommand : public boost::noncopyable - { - public: - enum Type - { - Type_OrthancRestApi, - Type_GetOrthancImage, - Type_GetOrthancWebViewerJpeg - }; - - virtual ~IOracleCommand() - { - } - - virtual Type GetType() const = 0; - }; - - - class IMessageEmitter : public boost::noncopyable - { - public: - virtual ~IMessageEmitter() - { - } - - virtual void EmitMessage(const OrthancStone::IObserver& observer, - const OrthancStone::IMessage& message) = 0; - }; - - - class IOracle : public boost::noncopyable - { - public: - virtual ~IOracle() - { - } - - virtual void Schedule(const OrthancStone::IObserver& receiver, - IOracleCommand* command) = 0; // Takes ownership - }; - - - - class IVolumeSlicer : public boost::noncopyable - { - public: - virtual ~IVolumeSlicer() - { - } - - virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) = 0; - }; - - - - class OracleCommandWithPayload : public IOracleCommand - { - private: - std::auto_ptr<Orthanc::IDynamicObject> payload_; - - public: - void SetPayload(Orthanc::IDynamicObject* payload) - { - if (payload == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - payload_.reset(payload); - } - } - - bool HasPayload() const - { - return (payload_.get() != NULL); - } - - const Orthanc::IDynamicObject& GetPayload() const - { - if (HasPayload()) - { - return *payload_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - }; - - - - class OracleCommandExceptionMessage : public OrthancStone::IMessage - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - const IOracleCommand& command_; - Orthanc::OrthancException exception_; - - public: - OracleCommandExceptionMessage(const IOracleCommand& command, - const Orthanc::OrthancException& exception) : - command_(command), - exception_(exception) - { - } - - OracleCommandExceptionMessage(const IOracleCommand& command, - const Orthanc::ErrorCode& error) : - command_(command), - exception_(error) - { - } - - const IOracleCommand& GetCommand() const - { - return command_; - } - - const Orthanc::OrthancException& GetException() const - { - return exception_; - } - }; - - - typedef std::map<std::string, std::string> HttpHeaders; - - class OrthancRestApiCommand : public OracleCommandWithPayload - { - public: - class SuccessMessage : public OrthancStone::OriginMessage<OrthancRestApiCommand> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - HttpHeaders headers_; - std::string answer_; - - public: - SuccessMessage(const OrthancRestApiCommand& command, - const HttpHeaders& answerHeaders, - std::string& answer /* will be swapped to avoid a memcpy() */) : - OriginMessage(command), - headers_(answerHeaders), - answer_(answer) - { - } - - const std::string& GetAnswer() const - { - return answer_; - } - - void ParseJsonBody(Json::Value& target) const - { - Json::Reader reader; - if (!reader.parse(answer_, target)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - const HttpHeaders& GetAnswerHeaders() const - { - return headers_; - } - }; - - - private: - Orthanc::HttpMethod method_; - std::string uri_; - std::string body_; - HttpHeaders headers_; - unsigned int timeout_; - - std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_; - std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> > failureCallback_; - - public: - OrthancRestApiCommand() : - method_(Orthanc::HttpMethod_Get), - uri_("/"), - timeout_(10) - { - } - - virtual Type GetType() const - { - return Type_OrthancRestApi; - } - - void SetMethod(Orthanc::HttpMethod method) - { - method_ = method; - } - - void SetUri(const std::string& uri) - { - uri_ = uri; - } - - void SetBody(const std::string& body) - { - body_ = body; - } - - void SetBody(const Json::Value& json) - { - Json::FastWriter writer; - body_ = writer.write(json); - } - - void SetHttpHeaders(const HttpHeaders& headers) - { - headers_ = headers; - } - - void SetHttpHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - Orthanc::HttpMethod GetMethod() const - { - return method_; - } - - const std::string& GetUri() const - { - return uri_; - } - - const std::string& GetBody() const - { - if (method_ == Orthanc::HttpMethod_Post || - method_ == Orthanc::HttpMethod_Put) - { - return body_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - const HttpHeaders& GetHttpHeaders() const - { - return headers_; - } - - void SetTimeout(unsigned int seconds) - { - timeout_ = seconds; - } - - unsigned int GetTimeout() const - { - return timeout_; - } - }; - - - - - class GetOrthancImageCommand : public OracleCommandWithPayload - { - public: - class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancImageCommand> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - std::auto_ptr<Orthanc::ImageAccessor> image_; - Orthanc::MimeType mime_; - - public: - SuccessMessage(const GetOrthancImageCommand& command, - Orthanc::ImageAccessor* image, // Takes ownership - Orthanc::MimeType mime) : - OriginMessage(command), - image_(image), - mime_(mime) - { - if (image == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - const Orthanc::ImageAccessor& GetImage() const - { - return *image_; - } - - Orthanc::MimeType GetMimeType() const - { - return mime_; - } - }; - - - private: - std::string uri_; - HttpHeaders headers_; - unsigned int timeout_; - bool hasExpectedFormat_; - Orthanc::PixelFormat expectedFormat_; - - std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_; - std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> > failureCallback_; - - public: - GetOrthancImageCommand() : - uri_("/"), - timeout_(10), - hasExpectedFormat_(false) - { - } - - virtual Type GetType() const - { - return Type_GetOrthancImage; - } - - void SetExpectedPixelFormat(Orthanc::PixelFormat format) - { - hasExpectedFormat_ = true; - expectedFormat_ = format; - } - - void SetUri(const std::string& uri) - { - uri_ = uri; - } - - void SetInstanceUri(const std::string& instance, - Orthanc::PixelFormat pixelFormat) - { - uri_ = "/instances/" + instance; - - switch (pixelFormat) - { - case Orthanc::PixelFormat_RGB24: - uri_ += "/preview"; - break; - - case Orthanc::PixelFormat_Grayscale16: - uri_ += "/image-uint16"; - break; - - case Orthanc::PixelFormat_SignedGrayscale16: - uri_ += "/image-int16"; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - void SetHttpHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - const std::string& GetUri() const - { - return uri_; - } - - const HttpHeaders& GetHttpHeaders() const - { - return headers_; - } - - void SetTimeout(unsigned int seconds) - { - timeout_ = seconds; - } - - unsigned int GetTimeout() const - { - return timeout_; - } - - void ProcessHttpAnswer(IMessageEmitter& emitter, - const OrthancStone::IObserver& receiver, - const std::string& answer, - const HttpHeaders& answerHeaders) const - { - Orthanc::MimeType contentType = Orthanc::MimeType_Binary; - - for (HttpHeaders::const_iterator it = answerHeaders.begin(); - it != answerHeaders.end(); ++it) - { - std::string s; - Orthanc::Toolbox::ToLowerCase(s, it->first); - - if (s == "content-type") - { - contentType = Orthanc::StringToMimeType(it->second); - break; - } - } - - std::auto_ptr<Orthanc::ImageAccessor> image; - - switch (contentType) - { - case Orthanc::MimeType_Png: - { - image.reset(new Orthanc::PngReader); - dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer); - break; - } - - case Orthanc::MimeType_Pam: - { - image.reset(new Orthanc::PamReader); - dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer); - break; - } - - case Orthanc::MimeType_Jpeg: - { - image.reset(new Orthanc::JpegReader); - dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, - "Unsupported HTTP Content-Type for an image: " + - std::string(Orthanc::EnumerationToString(contentType))); - } - - if (hasExpectedFormat_) - { - if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 && - image->GetFormat() == Orthanc::PixelFormat_Grayscale16) - { - image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); - } - - if (expectedFormat_ != image->GetFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - } - - SuccessMessage message(*this, image.release(), contentType); - emitter.EmitMessage(receiver, message); - } - }; - - - - class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload - { - public: - class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancWebViewerJpegCommand> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - std::auto_ptr<Orthanc::ImageAccessor> image_; - - public: - SuccessMessage(const GetOrthancWebViewerJpegCommand& command, - Orthanc::ImageAccessor* image) : // Takes ownership - OriginMessage(command), - image_(image) - { - if (image == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - const Orthanc::ImageAccessor& GetImage() const - { - return *image_; - } - }; - - private: - std::string instanceId_; - unsigned int frame_; - unsigned int quality_; - HttpHeaders headers_; - unsigned int timeout_; - Orthanc::PixelFormat expectedFormat_; - - std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_; - std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> > failureCallback_; - - public: - GetOrthancWebViewerJpegCommand() : - frame_(0), - quality_(95), - timeout_(10), - expectedFormat_(Orthanc::PixelFormat_Grayscale8) - { - } - - virtual Type GetType() const - { - return Type_GetOrthancWebViewerJpeg; - } - - void SetExpectedPixelFormat(Orthanc::PixelFormat format) - { - expectedFormat_ = format; - } - - void SetInstance(const std::string& instanceId) - { - instanceId_ = instanceId; - } - - void SetFrame(unsigned int frame) - { - frame_ = frame; - } - - void SetQuality(unsigned int quality) - { - if (quality <= 0 || - quality > 100) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - quality_ = quality; - } - } - - void SetHttpHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - Orthanc::PixelFormat GetExpectedPixelFormat() const - { - return expectedFormat_; - } - - const std::string& GetInstanceId() const - { - return instanceId_; - } - - unsigned int GetFrame() const - { - return frame_; - } - - unsigned int GetQuality() const - { - return quality_; - } - - const HttpHeaders& GetHttpHeaders() const - { - return headers_; - } - - void SetTimeout(unsigned int seconds) - { - timeout_ = seconds; - } - - unsigned int GetTimeout() const - { - return timeout_; - } - - std::string GetUri() const - { - return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) + - "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_)); - } - - void ProcessHttpAnswer(IMessageEmitter& emitter, - const OrthancStone::IObserver& receiver, - const std::string& answer) const - { - // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()" - - Json::Value encoded; - - { - Json::Reader reader; - if (!reader.parse(answer, encoded)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - if (encoded.type() != Json::objectValue || - !encoded.isMember("Orthanc") || - encoded["Orthanc"].type() != Json::objectValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - const Json::Value& info = encoded["Orthanc"]; - if (!info.isMember("PixelData") || - !info.isMember("Stretched") || - !info.isMember("Compression") || - info["Compression"].type() != Json::stringValue || - info["PixelData"].type() != Json::stringValue || - info["Stretched"].type() != Json::booleanValue || - info["Compression"].asString() != "Jpeg") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - bool isSigned = false; - bool isStretched = info["Stretched"].asBool(); - - if (info.isMember("IsSigned")) - { - if (info["IsSigned"].type() != Json::booleanValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - isSigned = info["IsSigned"].asBool(); - } - } - - std::auto_ptr<Orthanc::ImageAccessor> reader; - - { - std::string jpeg; - Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); - - reader.reset(new Orthanc::JpegReader); - dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg); - } - - if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image - { - if (expectedFormat_ != Orthanc::PixelFormat_RGB24) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - if (isSigned || isStretched) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - SuccessMessage message(*this, reader.release()); - emitter.EmitMessage(receiver, message); - return; - } - } - - if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - if (!isStretched) - { - if (expectedFormat_ != reader->GetFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - SuccessMessage message(*this, reader.release()); - emitter.EmitMessage(receiver, message); - return; - } - } - - int32_t stretchLow = 0; - int32_t stretchHigh = 0; - - if (!info.isMember("StretchLow") || - !info.isMember("StretchHigh") || - info["StretchLow"].type() != Json::intValue || - info["StretchHigh"].type() != Json::intValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - stretchLow = info["StretchLow"].asInt(); - stretchHigh = info["StretchHigh"].asInt(); - - if (stretchLow < -32768 || - stretchHigh > 65535 || - (stretchLow < 0 && stretchHigh > 32767)) - { - // This range cannot be represented with a uint16_t or an int16_t - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - // Decode a grayscale JPEG 8bpp image coming from the Web viewer - std::auto_ptr<Orthanc::ImageAccessor> image - (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false)); - - Orthanc::ImageProcessing::Convert(*image, *reader); - reader.reset(); - - float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; - - if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling)) - { - float offset = static_cast<float>(stretchLow) / scaling; - Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); - } - - SuccessMessage message(*this, image.release()); - emitter.EmitMessage(receiver, message); - } - }; - - - - - - class DicomInstanceParameters : - public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */ - { - private: - struct Data // Struct to ease the copy constructor - { - std::string orthancInstanceId_; - std::string studyInstanceUid_; - std::string seriesInstanceUid_; - std::string sopInstanceUid_; - Orthanc::DicomImageInformation imageInformation_; - OrthancStone::SopClassUid sopClassUid_; - double thickness_; - double pixelSpacingX_; - double pixelSpacingY_; - OrthancStone::CoordinateSystem3D geometry_; - OrthancStone::Vector frameOffsets_; - bool isColor_; - bool hasRescale_; - double rescaleIntercept_; - double rescaleSlope_; - bool hasDefaultWindowing_; - float defaultWindowingCenter_; - float defaultWindowingWidth_; - Orthanc::PixelFormat expectedPixelFormat_; - - void ComputeDoseOffsets(const Orthanc::DicomMap& dicom) - { - // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html - - { - std::string increment; - - if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) - { - Orthanc::Toolbox::ToUpperCase(increment); - if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag - { - LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; - return; - } - } - } - - if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || - frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) - { - LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; - frameOffsets_.clear(); - } - else - { - if (frameOffsets_.size() >= 2) - { - thickness_ = frameOffsets_[1] - frameOffsets_[0]; - - if (thickness_ < 0) - { - thickness_ = -thickness_; - } - } - } - } - - Data(const Orthanc::DicomMap& dicom) : - imageInformation_(dicom) - { - if (imageInformation_.GetNumberOfFrames() <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || - !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) || - !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - std::string s; - if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - sopClassUid_ = OrthancStone::StringToSopClassUid(s); - } - - if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) - { - thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); - } - - OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); - - std::string position, orientation; - if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && - dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) - { - geometry_ = OrthancStone::CoordinateSystem3D(position, orientation); - } - - if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) - { - ComputeDoseOffsets(dicom); - } - - isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && - imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); - - double doseGridScaling; - - if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && - dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) - { - hasRescale_ = true; - } - else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) - { - hasRescale_ = true; - rescaleIntercept_ = 0; - rescaleSlope_ = doseGridScaling; - } - else - { - hasRescale_ = false; - } - - OrthancStone::Vector c, w; - if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && - OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && - c.size() > 0 && - w.size() > 0) - { - hasDefaultWindowing_ = true; - defaultWindowingCenter_ = static_cast<float>(c[0]); - defaultWindowingWidth_ = static_cast<float>(w[0]); - } - else - { - hasDefaultWindowing_ = false; - } - - if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) - { - switch (imageInformation_.GetBitsStored()) - { - case 16: - expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; - break; - - case 32: - expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - else if (isColor_) - { - expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; - } - else if (imageInformation_.IsSigned()) - { - expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; - } - else - { - expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; - } - } - - OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const - { - if (frame == 0) - { - return geometry_; - } - else if (frame >= imageInformation_.GetNumberOfFrames()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) - { - if (frame >= frameOffsets_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - return OrthancStone::CoordinateSystem3D( - geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), - geometry_.GetAxisX(), - geometry_.GetAxisY()); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - // TODO - Is this necessary? - bool FrameContainsPlane(unsigned int frame, - const OrthancStone::CoordinateSystem3D& plane) const - { - if (frame >= imageInformation_.GetNumberOfFrames()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - OrthancStone::CoordinateSystem3D tmp = geometry_; - - if (frame != 0) - { - tmp = GetFrameGeometry(frame); - } - - double distance; - - return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) && - distance <= thickness_ / 2.0); - } - - - void ApplyRescale(Orthanc::ImageAccessor& image, - bool useDouble) const - { - if (image.GetFormat() != Orthanc::PixelFormat_Float32) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - if (hasRescale_) - { - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - float* p = reinterpret_cast<float*>(image.GetRow(y)); - - if (useDouble) - { - // Slower, accurate implementation using double - for (unsigned int x = 0; x < width; x++, p++) - { - double value = static_cast<double>(*p); - *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_); - } - } - else - { - // Fast, approximate implementation using float - for (unsigned int x = 0; x < width; x++, p++) - { - *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_); - } - } - } - } - } - }; - - - Data data_; - public: - DicomInstanceParameters(const DicomInstanceParameters& other) : - data_(other.data_) - { - } - - DicomInstanceParameters(const Orthanc::DicomMap& dicom) : - data_(dicom) - { - } - - void SetOrthancInstanceIdentifier(const std::string& id) - { - data_.orthancInstanceId_ = id; - } - - const std::string& GetOrthancInstanceIdentifier() const - { - return data_.orthancInstanceId_; - } - - const Orthanc::DicomImageInformation& GetImageInformation() const - { - return data_.imageInformation_; - } - - const std::string& GetStudyInstanceUid() const - { - return data_.studyInstanceUid_; - } - - const std::string& GetSeriesInstanceUid() const - { - return data_.seriesInstanceUid_; - } - - const std::string& GetSopInstanceUid() const - { - return data_.sopInstanceUid_; - } - - OrthancStone::SopClassUid GetSopClassUid() const - { - return data_.sopClassUid_; - } - - double GetThickness() const - { - return data_.thickness_; - } - - double GetPixelSpacingX() const - { - return data_.pixelSpacingX_; - } - - double GetPixelSpacingY() const - { - return data_.pixelSpacingY_; - } - - const OrthancStone::CoordinateSystem3D& GetGeometry() const - { - return data_.geometry_; - } - - OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const - { - return data_.GetFrameGeometry(frame); - } - - // TODO - Is this necessary? - bool FrameContainsPlane(unsigned int frame, - const OrthancStone::CoordinateSystem3D& plane) const - { - return data_.FrameContainsPlane(frame, plane); - } - - bool IsColor() const - { - return data_.isColor_; - } - - bool HasRescale() const - { - return data_.hasRescale_; - } - - double GetRescaleIntercept() const - { - if (data_.hasRescale_) - { - return data_.rescaleIntercept_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - double GetRescaleSlope() const - { - if (data_.hasRescale_) - { - return data_.rescaleSlope_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - bool HasDefaultWindowing() const - { - return data_.hasDefaultWindowing_; - } - - float GetDefaultWindowingCenter() const - { - if (data_.hasDefaultWindowing_) - { - return data_.defaultWindowingCenter_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - float GetDefaultWindowingWidth() const - { - if (data_.hasDefaultWindowing_) - { - return data_.defaultWindowingWidth_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - Orthanc::PixelFormat GetExpectedPixelFormat() const - { - return data_.expectedPixelFormat_; - } - - - OrthancStone::TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const - { - assert(sizeof(float) == 4); - - Orthanc::PixelFormat sourceFormat = source.GetFormat(); - - if (sourceFormat != GetExpectedPixelFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - if (sourceFormat == Orthanc::PixelFormat_RGB24) - { - // This is the case of a color image. No conversion has to be done. - return new OrthancStone::ColorTextureSceneLayer(source); - } - else - { - if (sourceFormat != Orthanc::PixelFormat_Grayscale16 && - sourceFormat != Orthanc::PixelFormat_Grayscale32 && - sourceFormat != Orthanc::PixelFormat_SignedGrayscale16) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - std::auto_ptr<OrthancStone::FloatTextureSceneLayer> texture; - - { - // This is the case of a grayscale frame. Convert it to Float32. - std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, - source.GetWidth(), - source.GetHeight(), - false)); - Orthanc::ImageProcessing::Convert(*converted, source); - - // Correct rescale slope/intercept if need be - data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32)); - - texture.reset(new OrthancStone::FloatTextureSceneLayer(*converted)); - } - - if (data_.hasDefaultWindowing_) - { - texture->SetCustomWindowing(data_.defaultWindowingCenter_, - data_.defaultWindowingWidth_); - } - - return texture.release(); - } - } - }; - - - class DicomVolumeImage : public boost::noncopyable - { - private: - std::auto_ptr<OrthancStone::ImageBuffer3D> image_; - std::vector<DicomInstanceParameters*> slices_; - uint64_t revision_; - std::vector<uint64_t> slicesRevision_; - std::vector<unsigned int> slicesQuality_; - - void CheckSlice(size_t index, - const DicomInstanceParameters& reference) const - { - const DicomInstanceParameters& slice = *slices_[index]; - - if (!OrthancStone::GeometryToolbox::IsParallel( - reference.GetGeometry().GetNormal(), - slice.GetGeometry().GetNormal())) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, - "A slice in the volume image is not parallel to the others"); - } - - if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, - "The pixel format changes across the slices of the volume image"); - } - - if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() || - reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize, - "The width/height of slices are not constant in the volume image"); - } - - if (!OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) || - !OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY())) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, - "The pixel spacing of the slices change across the volume image"); - } - } - - - void CheckVolume() const - { - for (size_t i = 0; i < slices_.size(); i++) - { - assert(slices_[i] != NULL); - if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, - "This class does not support multi-frame images"); - } - } - - if (slices_.size() != 0) - { - const DicomInstanceParameters& reference = *slices_[0]; - - for (size_t i = 1; i < slices_.size(); i++) - { - CheckSlice(i, reference); - } - } - } - - - void Clear() - { - image_.reset(); - - for (size_t i = 0; i < slices_.size(); i++) - { - assert(slices_[i] != NULL); - delete slices_[i]; - } - - slices_.clear(); - slicesRevision_.clear(); - slicesQuality_.clear(); - } - - - void CheckSliceIndex(size_t index) const - { - assert(slices_.size() == image_->GetDepth() && - slices_.size() == slicesRevision_.size()); - - if (!HasGeometry()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else if (index >= slices_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - - public: - DicomVolumeImage() - { - } - - ~DicomVolumeImage() - { - Clear(); - } - - // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" - void SetGeometry(OrthancStone::SlicesSorter& slices) - { - Clear(); - - if (!slices.Sort()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, - "Cannot sort the 3D slices of a DICOM series"); - } - - if (slices.GetSlicesCount() == 0) - { - // Empty volume - image_.reset(new OrthancStone::ImageBuffer3D(Orthanc::PixelFormat_Grayscale8, 0, 0, 0, - false /* don't compute range */)); - } - else - { - slices_.reserve(slices.GetSlicesCount()); - slicesRevision_.resize(slices.GetSlicesCount(), 0); - slicesQuality_.resize(slices.GetSlicesCount(), 0); - - for (size_t i = 0; i < slices.GetSlicesCount(); i++) - { - const DicomInstanceParameters& slice = - dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i)); - slices_.push_back(new DicomInstanceParameters(slice)); - } - - CheckVolume(); - - const double spacingZ = slices.ComputeSpacingBetweenSlices(); - LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; - - const DicomInstanceParameters& parameters = *slices_[0]; - - image_.reset(new OrthancStone::ImageBuffer3D(parameters.GetExpectedPixelFormat(), - parameters.GetImageInformation().GetWidth(), - parameters.GetImageInformation().GetHeight(), - slices.GetSlicesCount(), false /* don't compute range */)); - - image_->GetGeometry().SetAxialGeometry(slices.GetSliceGeometry(0)); - image_->GetGeometry().SetVoxelDimensions(parameters.GetPixelSpacingX(), - parameters.GetPixelSpacingY(), spacingZ); - } - - image_->Clear(); - - revision_++; - } - - uint64_t GetRevision() const - { - return revision_; - } - - bool HasGeometry() const - { - return (image_.get() != NULL); - } - - const OrthancStone::ImageBuffer3D& GetImage() const - { - if (!HasGeometry()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - return *image_; - } - } - - size_t GetSlicesCount() const - { - if (!HasGeometry()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - return slices_.size(); - } - } - - const DicomInstanceParameters& GetSliceParameters(size_t index) const - { - CheckSliceIndex(index); - return *slices_[index]; - } - - uint64_t GetSliceRevision(size_t index) const - { - CheckSliceIndex(index); - return slicesRevision_[index]; - } - - void SetSliceContent(size_t index, - const Orthanc::ImageAccessor& image, - unsigned int quality) - { - CheckSliceIndex(index); - - // If a better image quality is already available, don't update the content - if (quality >= slicesQuality_[index]) - { - { - OrthancStone::ImageBuffer3D::SliceWriter writer - (*image_, OrthancStone::VolumeProjection_Axial, index); - Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image); - } - - revision_ ++; - slicesRevision_[index] += 1; - } - } - }; - - - - class IDicomVolumeSource : public boost::noncopyable - { - public: - virtual ~IDicomVolumeSource() - { - } - - virtual const DicomVolumeImage& GetVolume() const = 0; - - virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) = 0; - }; - - - - class VolumeSeriesOrthancLoader : - public OrthancStone::IObserver, - public IDicomVolumeSource - { - private: - static const unsigned int LOW_QUALITY = 0; - static const unsigned int MIDDLE_QUALITY = 1; - static const unsigned int BEST_QUALITY = 2; - - - static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command) - { - return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue(); - } - - - void ScheduleNextSliceDownload() - { - assert(strategy_.get() != NULL); - - unsigned int sliceIndex, quality; - - if (strategy_->GetNext(sliceIndex, quality)) - { - assert(quality <= BEST_QUALITY); - - const DicomInstanceParameters& slice = volume_.GetSliceParameters(sliceIndex); - - const std::string& instance = slice.GetOrthancInstanceIdentifier(); - if (instance.empty()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - std::auto_ptr<Refactoring::OracleCommandWithPayload> command; - - if (quality == BEST_QUALITY) - { - std::auto_ptr<Refactoring::GetOrthancImageCommand> tmp( - new Refactoring::GetOrthancImageCommand); - tmp->SetHttpHeader("Accept-Encoding", "gzip"); - tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); - tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat()); - tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); - command.reset(tmp.release()); - } - else - { - std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand> tmp( - new Refactoring::GetOrthancWebViewerJpegCommand); - tmp->SetHttpHeader("Accept-Encoding", "gzip"); - tmp->SetInstance(instance); - tmp->SetQuality((quality == 0 ? 50 : 90)); - tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); - command.reset(tmp.release()); - } - - command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex)); - oracle_.Schedule(*this, command.release()); - } - } - - - void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message) - { - Json::Value body; - message.ParseJsonBody(body); - - if (body.type() != Json::objectValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - { - Json::Value::Members instances = body.getMemberNames(); - - OrthancStone::SlicesSorter slices; - - for (size_t i = 0; i < instances.size(); i++) - { - Orthanc::DicomMap dicom; - dicom.FromDicomAsJson(body[instances[i]]); - - std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom)); - instance->SetOrthancInstanceIdentifier(instances[i]); - - OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry(); - slices.AddSlice(geometry, instance.release()); - } - - volume_.SetGeometry(slices); - } - - if (volume_.GetSlicesCount() != 0) - { - strategy_.reset(new OrthancStone::BasicFetchingStrategy( - new OrthancStone::BasicFetchingItemsSorter(volume_.GetSlicesCount()), BEST_QUALITY)); - - for (unsigned int i = 0; i < 4; i++) // Schedule up to 4 simultaneous downloads (TODO - parameter) - { - ScheduleNextSliceDownload(); - } - } - } - - - void LoadBestQualitySliceContent(const Refactoring::GetOrthancImageCommand::SuccessMessage& message) - { - volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), - message.GetImage(), BEST_QUALITY); - - ScheduleNextSliceDownload(); - } - - - void LoadJpegSliceContent(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message) - { - unsigned int quality; - - switch (message.GetOrigin().GetQuality()) - { - case 50: - quality = LOW_QUALITY; - break; - - case 90: - quality = MIDDLE_QUALITY; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality); - - ScheduleNextSliceDownload(); - } - - - IOracle& oracle_; - bool active_; - DicomVolumeImage volume_; - - std::auto_ptr<OrthancStone::IFetchingStrategy> strategy_; - - public: - VolumeSeriesOrthancLoader(IOracle& oracle, - OrthancStone::IObservable& oracleObservable) : - IObserver(oracleObservable.GetBroker()), - oracle_(oracle), - active_(false) - { - oracleObservable.RegisterObserverCallback( - new OrthancStone::Callable<VolumeSeriesOrthancLoader, OrthancRestApiCommand::SuccessMessage> - (*this, &VolumeSeriesOrthancLoader::LoadGeometry)); - - oracleObservable.RegisterObserverCallback( - new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancImageCommand::SuccessMessage> - (*this, &VolumeSeriesOrthancLoader::LoadBestQualitySliceContent)); - - oracleObservable.RegisterObserverCallback( - new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancWebViewerJpegCommand::SuccessMessage> - (*this, &VolumeSeriesOrthancLoader::LoadJpegSliceContent)); - } - - void LoadSeries(const std::string& seriesId) - { - if (active_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - active_ = true; - - std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); - command->SetUri("/series/" + seriesId + "/instances-tags"); - - oracle_.Schedule(*this, command.release()); - } - - - virtual const DicomVolumeImage& GetVolume() const - { - return volume_; - } - - - virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) - { - if (strategy_.get() == NULL) - { - // Should have called GetVolume().HasGeometry() before - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - strategy_->SetCurrent(sliceIndex); - } - } - }; - - - -#if 0 - void LoadInstance(const std::string& instanceId) - { - if (active_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - active_ = true; - - // Tag "3004-000c" is "Grid Frame Offset Vector", which is - // mandatory to read RT DOSE, but is too long to be returned by default - - // TODO => Should be part of a second call if needed - - std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); - command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c"); - command->SetPayload(new LoadInstanceGeometryHandler(*this)); - - oracle_.Schedule(*this, command.release()); - } -#endif - - - /* class VolumeSlicerBase : public IVolumeSlicer - { - private: - OrthancStone::Scene2D& scene_; - int layerDepth_; - bool first_; - OrthancStone::CoordinateSystem3D lastPlane_; - - protected: - bool HasViewportPlaneChanged(const OrthancStone::CoordinateSystem3D& plane) const - { - if (first_ || - !OrthancStone::LinearAlgebra::IsCloseToZero( - boost::numeric::ublas::norm_2(lastPlane_.GetNormal() - plane.GetNormal()))) - { - // This is the first rendering, or the plane has not the same orientation - return false; - } - else - { - double offset1 = lastPlane_.ProjectAlongNormal(plane.GetOrigin()); - double offset2 = lastPlane_.ProjectAlongNormal(lastPlane_.GetOrigin()); - return OrthancStone::LinearAlgebra::IsCloseToZero(offset2 - offset1); - } - } - - void SetLastViewportPlane(const OrthancStone::CoordinateSystem3D& plane) - { - first_ = false; - lastPlane_ = plane; - } - - void SetLayer(OrthancStone::ISceneLayer* layer) - { - scene_.SetLayer(layerDepth_, layer); - } - - void DeleteLayer() - { - scene_.DeleteLayer(layerDepth_); - } - - public: - VolumeSlicerBase(OrthancStone::Scene2D& scene, - int layerDepth) : - scene_(scene), - layerDepth_(layerDepth), - first_(true) - { - } - };*/ - - - - class DicomVolumeMPRSlicer : public IVolumeSlicer - { - private: - bool linearInterpolation_; - OrthancStone::Scene2D& scene_; - int layerDepth_; - IDicomVolumeSource& source_; - bool first_; - OrthancStone::VolumeProjection lastProjection_; - unsigned int lastSliceIndex_; - uint64_t lastSliceRevision_; - - public: - DicomVolumeMPRSlicer(OrthancStone::Scene2D& scene, - int layerDepth, - IDicomVolumeSource& source) : - linearInterpolation_(false), - scene_(scene), - layerDepth_(layerDepth), - source_(source), - first_(true) - { - } - - void SetLinearInterpolation(bool enabled) - { - linearInterpolation_ = enabled; - } - - bool IsLinearInterpolation() const - { - return linearInterpolation_; - } - - virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) - { - if (!source_.GetVolume().HasGeometry() || - source_.GetVolume().GetSlicesCount() == 0) - { - scene_.DeleteLayer(layerDepth_); - return; - } - - const OrthancStone::VolumeImageGeometry& geometry = source_.GetVolume().GetImage().GetGeometry(); - - OrthancStone::VolumeProjection projection; - unsigned int sliceIndex; - if (!geometry.DetectSlice(projection, sliceIndex, plane)) - { - // The cutting plane is neither axial, nor coronal, nor - // sagittal. Could use "VolumeReslicer" here. - scene_.DeleteLayer(layerDepth_); - return; - } - - uint64_t sliceRevision; - if (projection == OrthancStone::VolumeProjection_Axial) - { - sliceRevision = source_.GetVolume().GetSliceRevision(sliceIndex); - - if (first_ || - lastSliceIndex_ != sliceIndex) - { - // Reorder the prefetching queue - source_.NotifyAxialSliceAccessed(sliceIndex); - } - } - else - { - // For coronal and sagittal projections, we take the global - // revision of the volume - sliceRevision = source_.GetVolume().GetRevision(); - } - - if (first_ || - lastProjection_ != projection || - lastSliceIndex_ != sliceIndex || - lastSliceRevision_ != sliceRevision) - { - // Either the viewport plane, or the content of the slice have not - // changed since the last time the layer was set: Update is needed - - first_ = false; - lastProjection_ = projection; - lastSliceIndex_ = sliceIndex; - lastSliceRevision_ = sliceRevision; - - std::auto_ptr<OrthancStone::TextureBaseSceneLayer> texture; - - { - const DicomInstanceParameters& parameters = source_.GetVolume().GetSliceParameters - (projection == OrthancStone::VolumeProjection_Axial ? sliceIndex : 0); - - OrthancStone::ImageBuffer3D::SliceReader reader(source_.GetVolume().GetImage(), projection, sliceIndex); - texture.reset(parameters.CreateTexture(reader.GetAccessor())); - } - - const OrthancStone::CoordinateSystem3D& system = geometry.GetProjectionGeometry(projection); - - double x0, y0, x1, y1; - system.ProjectPoint(x0, y0, system.GetOrigin()); - system.ProjectPoint(x0, y0, system.GetOrigin() + system.GetAxisX()); - texture->SetOrigin(x0, y0); - - double dx = x1 - x0; - double dy = y1 - y0; - if (!OrthancStone::LinearAlgebra::IsCloseToZero(dx) || - !OrthancStone::LinearAlgebra::IsCloseToZero(dy)) - { - texture->SetAngle(atan2(dy, dx)); - } - - OrthancStone::Vector tmp; - geometry.GetVoxelDimensions(projection); - texture->SetPixelSpacing(tmp[0], tmp[1]); - - texture->SetLinearInterpolation(linearInterpolation_); - - scene_.SetLayer(layerDepth_, texture.release()); - } - } - }; - - - - - - class NativeOracle : public IOracle - { - private: - class Item : public Orthanc::IDynamicObject - { - private: - const OrthancStone::IObserver& receiver_; - std::auto_ptr<IOracleCommand> command_; - - public: - Item(const OrthancStone::IObserver& receiver, - IOracleCommand* command) : - receiver_(receiver), - command_(command) - { - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - const OrthancStone::IObserver& GetReceiver() const - { - return receiver_; - } - - const IOracleCommand& GetCommand() const - { - assert(command_.get() != NULL); - return *command_; - } - }; - - - enum State - { - State_Setup, - State_Running, - State_Stopped - }; - - - IMessageEmitter& emitter_; - Orthanc::WebServiceParameters orthanc_; - Orthanc::SharedMessageQueue queue_; - State state_; - boost::mutex mutex_; - std::vector<boost::thread*> workers_; - - - void CopyHttpHeaders(Orthanc::HttpClient& client, - const HttpHeaders& headers) - { - for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ ) - { - client.AddHeader(it->first, it->second); - } - } - - - void DecodeAnswer(std::string& answer, - const HttpHeaders& headers) - { - Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None; - - for (HttpHeaders::const_iterator it = headers.begin(); - it != headers.end(); ++it) - { - std::string s; - Orthanc::Toolbox::ToLowerCase(s, it->first); - - if (s == "content-encoding") - { - if (it->second == "gzip") - { - contentEncoding = Orthanc::HttpCompression_Gzip; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, - "Unsupported HTTP Content-Encoding: " + it->second); - } - - break; - } - } - - if (contentEncoding == Orthanc::HttpCompression_Gzip) - { - std::string compressed; - answer.swap(compressed); - - Orthanc::GzipCompressor compressor; - compressor.Uncompress(answer, compressed.c_str(), compressed.size()); - } - } - - - void Execute(const OrthancStone::IObserver& receiver, - const OrthancRestApiCommand& command) - { - Orthanc::HttpClient client(orthanc_, command.GetUri()); - client.SetMethod(command.GetMethod()); - client.SetTimeout(command.GetTimeout()); - - CopyHttpHeaders(client, command.GetHttpHeaders()); - - if (command.GetMethod() == Orthanc::HttpMethod_Post || - command.GetMethod() == Orthanc::HttpMethod_Put) - { - client.SetBody(command.GetBody()); - } - - std::string answer; - HttpHeaders answerHeaders; - client.ApplyAndThrowException(answer, answerHeaders); - - DecodeAnswer(answer, answerHeaders); - - OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer); - emitter_.EmitMessage(receiver, message); - } - - - void Execute(const OrthancStone::IObserver& receiver, - const GetOrthancImageCommand& command) - { - Orthanc::HttpClient client(orthanc_, command.GetUri()); - client.SetTimeout(command.GetTimeout()); - - CopyHttpHeaders(client, command.GetHttpHeaders()); - - std::string answer; - HttpHeaders answerHeaders; - client.ApplyAndThrowException(answer, answerHeaders); - - DecodeAnswer(answer, answerHeaders); - - command.ProcessHttpAnswer(emitter_, receiver, answer, answerHeaders); - } - - - void Execute(const OrthancStone::IObserver& receiver, - const GetOrthancWebViewerJpegCommand& command) - { - Orthanc::HttpClient client(orthanc_, command.GetUri()); - client.SetTimeout(command.GetTimeout()); - - CopyHttpHeaders(client, command.GetHttpHeaders()); - - std::string answer; - HttpHeaders answerHeaders; - client.ApplyAndThrowException(answer, answerHeaders); - - DecodeAnswer(answer, answerHeaders); - - command.ProcessHttpAnswer(emitter_, receiver, answer); - } - - - void Step() - { - std::auto_ptr<Orthanc::IDynamicObject> object(queue_.Dequeue(100)); - - if (object.get() != NULL) - { - const Item& item = dynamic_cast<Item&>(*object); - - try - { - switch (item.GetCommand().GetType()) - { - case IOracleCommand::Type_OrthancRestApi: - Execute(item.GetReceiver(), - dynamic_cast<const OrthancRestApiCommand&>(item.GetCommand())); - break; - - case IOracleCommand::Type_GetOrthancImage: - Execute(item.GetReceiver(), - dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand())); - break; - - case IOracleCommand::Type_GetOrthancWebViewerJpeg: - Execute(item.GetReceiver(), - dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand())); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "Exception within the oracle: " << e.What(); - emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e)); - } - catch (...) - { - LOG(ERROR) << "Native exception within the oracle"; - emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage - (item.GetCommand(), Orthanc::ErrorCode_InternalError)); - } - } - } - - - static void Worker(NativeOracle* that) - { - assert(that != NULL); - - for (;;) - { - { - boost::mutex::scoped_lock lock(that->mutex_); - if (that->state_ != State_Running) - { - return; - } - } - - that->Step(); - } - } - - - void StopInternal() - { - { - boost::mutex::scoped_lock lock(mutex_); - - if (state_ == State_Setup || - state_ == State_Stopped) - { - return; - } - else - { - state_ = State_Stopped; - } - } - - for (size_t i = 0; i < workers_.size(); i++) - { - if (workers_[i] != NULL) - { - if (workers_[i]->joinable()) - { - workers_[i]->join(); - } - - delete workers_[i]; - } - } - } - - - public: - NativeOracle(IMessageEmitter& emitter) : - emitter_(emitter), - state_(State_Setup), - workers_(4) - { - } - - virtual ~NativeOracle() - { - StopInternal(); - } - - void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc) - { - boost::mutex::scoped_lock lock(mutex_); - - if (state_ != State_Setup) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - orthanc_ = orthanc; - } - } - - void SetWorkersCount(unsigned int count) - { - boost::mutex::scoped_lock lock(mutex_); - - if (count <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else if (state_ != State_Setup) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - workers_.resize(count); - } - } - - void Start() - { - boost::mutex::scoped_lock lock(mutex_); - - if (state_ != State_Setup) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - state_ = State_Running; - - for (unsigned int i = 0; i < workers_.size(); i++) - { - workers_[i] = new boost::thread(Worker, this); - } - } - } - - void Stop() - { - StopInternal(); - } - - virtual void Schedule(const OrthancStone::IObserver& receiver, - IOracleCommand* command) - { - queue_.Enqueue(new Item(receiver, command)); - } - }; - - +namespace OrthancStone +{ class NativeApplicationContext : public IMessageEmitter { private: - boost::shared_mutex mutex_; - OrthancStone::MessageBroker broker_; - OrthancStone::IObservable oracleObservable_; + boost::shared_mutex mutex_; + MessageBroker broker_; + IObservable oracleObservable_; public: NativeApplicationContext() : @@ -2265,8 +56,8 @@ } - virtual void EmitMessage(const OrthancStone::IObserver& observer, - const OrthancStone::IMessage& message) + virtual void EmitMessage(const IObserver& observer, + const IMessage& message) ORTHANC_OVERRIDE { try { @@ -2308,12 +99,12 @@ { } - OrthancStone::MessageBroker& GetBroker() + MessageBroker& GetBroker() { return that_.broker_; } - OrthancStone::IObservable& GetOracleObservable() + IObservable& GetOracleObservable() { return that_.oracleObservable_; } @@ -2326,7 +117,88 @@ class Toto : public OrthancStone::IObserver { private: - void Handle(const Refactoring::OrthancRestApiCommand::SuccessMessage& message) + OrthancStone::CoordinateSystem3D plane_; + OrthancStone::IOracle& oracle_; + OrthancStone::Scene2D scene_; + std::auto_ptr<OrthancStone::VolumeSceneLayerSource> source1_, source2_, source3_; + + + void Refresh() + { + if (source1_.get() != NULL) + { + source1_->Update(plane_); + } + + if (source2_.get() != NULL) + { + source2_->Update(plane_); + } + + if (source3_.get() != NULL) + { + source3_->Update(plane_); + } + + scene_.FitContent(1024, 768); + + { + OrthancStone::CairoCompositor compositor(scene_, 1024, 768); + compositor.Refresh(); + + Orthanc::ImageAccessor accessor; + compositor.GetCanvas().GetReadOnlyAccessor(accessor); + + Orthanc::Image tmp(Orthanc::PixelFormat_RGB24, accessor.GetWidth(), accessor.GetHeight(), false); + Orthanc::ImageProcessing::Convert(tmp, accessor); + + static unsigned int count = 0; + char buf[64]; + sprintf(buf, "scene-%06d.png", count++); + + Orthanc::PngWriter writer; + writer.WriteToFile(buf, tmp); + } + } + + + void Handle(const OrthancStone::DicomVolumeImage::GeometryReadyMessage& message) + { + printf("Geometry ready\n"); + + plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry(); + //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry(); + //plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry(); + plane_.SetOrigin(message.GetOrigin().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f)); + + Refresh(); + } + + + void Handle(const OrthancStone::SleepOracleCommand::TimeoutMessage& message) + { + if (message.GetOrigin().HasPayload()) + { + printf("TIMEOUT! %d\n", dynamic_cast<const Orthanc::SingleValueObject<unsigned int>& >(message.GetOrigin().GetPayload()).GetValue()); + } + else + { + printf("TIMEOUT\n"); + + Refresh(); + + /** + * The sleep() leads to a crash if the oracle is still running, + * while this object is destroyed. Always stop the oracle before + * destroying active objects. (*) + **/ + // boost::this_thread::sleep(boost::posix_time::seconds(2)); + + oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(message.GetOrigin().GetDelay())); + } + } + + void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { Json::Value v; message.ParseJsonBody(v); @@ -2334,24 +206,24 @@ printf("ICI [%s]\n", v.toStyledString().c_str()); } - void Handle(const Refactoring::GetOrthancImageCommand::SuccessMessage& message) + void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message) { printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); } - void Handle(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message) + void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message) { printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); } - void Handle(const Refactoring::OracleCommandExceptionMessage& message) + void Handle(const OrthancStone::OracleCommandExceptionMessage& message) { printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType()); switch (message.GetCommand().GetType()) { - case Refactoring::IOracleCommand::Type_GetOrthancWebViewerJpeg: - printf("URI: [%s]\n", dynamic_cast<const Refactoring::GetOrthancWebViewerJpegCommand&> + case OrthancStone::IOracleCommand::Type_GetOrthancWebViewerJpeg: + printf("URI: [%s]\n", dynamic_cast<const OrthancStone::GetOrthancWebViewerJpegCommand&> (message.GetCommand()).GetUri().c_str()); break; @@ -2361,48 +233,133 @@ } public: - Toto(OrthancStone::IObservable& oracle) : - IObserver(oracle.GetBroker()) + Toto(OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable) : + IObserver(oracleObservable.GetBroker()), + oracle_(oracle) { - oracle.RegisterObserverCallback + oracleObservable.RegisterObserverCallback + (new OrthancStone::Callable + <Toto, OrthancStone::SleepOracleCommand::TimeoutMessage>(*this, &Toto::Handle)); + + oracleObservable.RegisterObserverCallback + (new OrthancStone::Callable + <Toto, OrthancStone::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle)); + + oracleObservable.RegisterObserverCallback (new OrthancStone::Callable - <Toto, Refactoring::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle)); + <Toto, OrthancStone::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle)); + + oracleObservable.RegisterObserverCallback + (new OrthancStone::Callable + <Toto, OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle)); - oracle.RegisterObserverCallback + oracleObservable.RegisterObserverCallback + (new OrthancStone::Callable + <Toto, OrthancStone::OracleCommandExceptionMessage>(*this, &Toto::Handle)); + } + + void SetReferenceLoader(OrthancStone::IObservable& loader) + { + loader.RegisterObserverCallback (new OrthancStone::Callable - <Toto, Refactoring::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle)); + <Toto, OrthancStone::DicomVolumeImage::GeometryReadyMessage>(*this, &Toto::Handle)); + } + + void SetVolume1(int depth, + const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + source1_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); + + if (style != NULL) + { + source1_->SetConfigurator(style); + } + } - oracle.RegisterObserverCallback - (new OrthancStone::Callable - <Toto, Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle)); + void SetVolume2(int depth, + const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + source2_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); - oracle.RegisterObserverCallback - (new OrthancStone::Callable - <Toto, Refactoring::OracleCommandExceptionMessage>(*this, &Toto::Handle)); + if (style != NULL) + { + source2_->SetConfigurator(style); + } } + + void SetStructureSet(int depth, + const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume) + { + source3_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); + } + }; -void Run(Refactoring::NativeApplicationContext& context, - Refactoring::IOracle& oracle) +void Run(OrthancStone::NativeApplicationContext& context, + OrthancStone::ThreadedOracle& oracle) { - std::auto_ptr<Toto> toto; - std::auto_ptr<Refactoring::VolumeSeriesOrthancLoader> loader1, loader2; + // the oracle has been supplied with the context (as an IEmitter) upon + // creation + boost::shared_ptr<OrthancStone::DicomVolumeImage> ct(new OrthancStone::DicomVolumeImage); + boost::shared_ptr<OrthancStone::DicomVolumeImage> dose(new OrthancStone::DicomVolumeImage); + + + boost::shared_ptr<Toto> toto; + boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader; + boost::shared_ptr<OrthancStone::OrthancMultiframeVolumeLoader> doseLoader; + boost::shared_ptr<OrthancStone::DicomStructureSetLoader> rtstructLoader; { - Refactoring::NativeApplicationContext::WriterLock lock(context); - toto.reset(new Toto(lock.GetOracleObservable())); - loader1.reset(new Refactoring::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable())); - loader2.reset(new Refactoring::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable())); + OrthancStone::NativeApplicationContext::WriterLock lock(context); + toto.reset(new Toto(oracle, lock.GetOracleObservable())); + + // the oracle is used to schedule commands + // the oracleObservable is used by the loaders to: + // - request the broker (lifetime mgmt) + // - register the loader callbacks (called indirectly by the oracle) + ctLoader.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct, oracle, lock.GetOracleObservable())); + doseLoader.reset(new OrthancStone::OrthancMultiframeVolumeLoader(dose, oracle, lock.GetOracleObservable())); + rtstructLoader.reset(new OrthancStone::DicomStructureSetLoader(oracle, lock.GetOracleObservable())); } - if (1) + + //toto->SetReferenceLoader(*ctLoader); + toto->SetReferenceLoader(*doseLoader); + + +#if 1 + toto->SetVolume1(0, ctLoader, new OrthancStone::GrayscaleStyleConfigurator); +#else + { + boost::shared_ptr<OrthancStone::IVolumeSlicer> reslicer(new OrthancStone::DicomVolumeImageReslicer(ct)); + toto->SetVolume1(0, reslicer, new OrthancStone::GrayscaleStyleConfigurator); + } +#endif + + + { + std::auto_ptr<OrthancStone::LookupTableStyleConfigurator> config(new OrthancStone::LookupTableStyleConfigurator); + config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); + + boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> tmp(new OrthancStone::DicomVolumeImageMPRSlicer(dose)); + toto->SetVolume2(1, tmp, config.release()); + } + + toto->SetStructureSet(2, rtstructLoader); + + oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100)); + + if (0) { Json::Value v = Json::objectValue; v["Level"] = "Series"; v["Query"] = Json::objectValue; - std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); + std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); command->SetMethod(Orthanc::HttpMethod_Post); command->SetUri("/tools/find"); command->SetBody(v); @@ -2410,67 +367,112 @@ oracle.Schedule(*toto, command.release()); } - if (1) - { - std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); - command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg))); - command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); - oracle.Schedule(*toto, command.release()); - } - - if (1) + if(0) { - std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); - command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); - command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); - oracle.Schedule(*toto, command.release()); - } - - if (1) - { - std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); - command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); - command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); - oracle.Schedule(*toto, command.release()); + if (0) + { + std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); + command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg))); + command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); + oracle.Schedule(*toto, command.release()); + } + + if (0) + { + std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); + command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); + command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); + oracle.Schedule(*toto, command.release()); + } + + if (0) + { + std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); + command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); + command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); + oracle.Schedule(*toto, command.release()); + } + + if (0) + { + std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); + command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); + oracle.Schedule(*toto, command.release()); + } + + if (0) + { + std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); + command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); + command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); + oracle.Schedule(*toto, command.release()); + } + + if (0) + { + std::auto_ptr<OrthancStone::GetOrthancWebViewerJpegCommand> command(new OrthancStone::GetOrthancWebViewerJpegCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e"); + command->SetQuality(90); + oracle.Schedule(*toto, command.release()); + } + + + if (0) + { + for (unsigned int i = 0; i < 10; i++) + { + std::auto_ptr<OrthancStone::SleepOracleCommand> command(new OrthancStone::SleepOracleCommand(i * 1000)); + command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(42 * i)); + oracle.Schedule(*toto, command.release()); + } + } } - if (1) - { - std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); - command->SetHttpHeader("Accept-Encoding", "gzip"); - command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); - command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); - oracle.Schedule(*toto, command.release()); - } + // 2017-11-17-Anonymized +#if 0 + // BGO data + ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT + doseLoader->LoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // RT-DOSE + //rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT +#else + //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT + //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE + //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6"); // RT-STRUCT + + // 2017-05-16 + ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT + doseLoader->LoadInstance("eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad"); // RT-DOSE + rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT +#endif + // 2015-01-28-Multiframe + //doseLoader->LoadInstance("88f71e2a-5fad1c61-96ed14d6-5b3d3cf7-a5825279"); // Multiframe CT - if (1) - { - std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); - command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); - command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); - oracle.Schedule(*toto, command.release()); - } - - if (1) - { - std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand> command(new Refactoring::GetOrthancWebViewerJpegCommand); - command->SetHttpHeader("Accept-Encoding", "gzip"); - command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e"); - command->SetQuality(90); - oracle.Schedule(*toto, command.release()); - } + // Delphine + //ctLoader->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT + //ctLoader->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); // Lung 1/10mm - // 2017-11-17-Anonymized - //loader1->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT - //loader2->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE + { + LOG(WARNING) << "...Waiting for Ctrl-C..."; + + oracle.Start(); + + Orthanc::SystemToolbox::ServerBarrier(); - // Delphine - loader1->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT + /** + * WARNING => The oracle must be stopped BEFORE the objects using + * it are destroyed!!! This forces to wait for the completion of + * the running callback methods. Otherwise, the callbacks methods + * might still be running while their parent object is destroyed, + * resulting in crashes. This is very visible if adding a sleep(), + * as in (*). + **/ - LOG(WARNING) << "...Waiting for Ctrl-C..."; - Orthanc::SystemToolbox::ServerBarrier(); - //boost::this_thread::sleep(boost::posix_time::seconds(1)); + oracle.Stop(); + } } @@ -2483,13 +485,14 @@ int main(int argc, char* argv[]) { OrthancStone::StoneInitialize(); - Orthanc::Logging::EnableInfoLevel(true); + //Orthanc::Logging::EnableInfoLevel(true); try { - Refactoring::NativeApplicationContext context; + OrthancStone::NativeApplicationContext context; - Refactoring::NativeOracle oracle(context); + OrthancStone::ThreadedOracle oracle(context); + //oracle.SetThreadsCount(1); { Orthanc::WebServiceParameters p; @@ -2498,11 +501,11 @@ oracle.SetOrthancParameters(p); } - oracle.Start(); + //oracle.Start(); Run(context, oracle); - - oracle.Stop(); + + //oracle.Stop(); } catch (Orthanc::OrthancException& e) {
--- a/Samples/Sdl/TrackerSample.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Samples/Sdl/TrackerSample.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -48,18 +48,11 @@ */ - -using namespace Orthanc; -using namespace OrthancStone; - - - - -boost::weak_ptr<TrackerSampleApp> g_app; +boost::weak_ptr<OrthancStone::TrackerSampleApp> g_app; void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value) { - boost::shared_ptr<TrackerSampleApp> app = g_app.lock(); + boost::shared_ptr<OrthancStone::TrackerSampleApp> app = g_app.lock(); if (app) { app->SetInfoDisplayMessage(key, value); @@ -73,9 +66,11 @@ **/ int main(int argc, char* argv[]) { + using namespace OrthancStone; + StoneInitialize(); Orthanc::Logging::EnableInfoLevel(true); - Orthanc::Logging::EnableTraceLevel(true); +// Orthanc::Logging::EnableTraceLevel(true); try {
--- a/Samples/Sdl/TrackerSampleApp.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Samples/Sdl/TrackerSampleApp.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -20,20 +20,19 @@ #include "TrackerSampleApp.h" -#include <Framework/Scene2DViewport/CreateLineMeasureTracker.h> -#include <Framework/Scene2DViewport/CreateAngleMeasureTracker.h> +#include "../../Applications/Sdl/SdlOpenGLWindow.h" -#include <Framework/Scene2D/PanSceneTracker.h> -#include <Framework/Scene2D/RotateSceneTracker.h> -#include <Framework/Scene2D/Scene2D.h> -#include <Framework/Scene2D/ZoomSceneTracker.h> -#include <Framework/Scene2D/CairoCompositor.h> -#include <Framework/Scene2D/ColorTextureSceneLayer.h> -#include <Framework/Scene2D/OpenGLCompositor.h> - -#include <Framework/StoneInitialization.h> - -#include <Applications/Sdl/SdlOpenGLWindow.h> +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/Scene2D.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" +#include "../../Framework/Scene2DViewport/CreateAngleMeasureTracker.h" +#include "../../Framework/Scene2DViewport/CreateLineMeasureTracker.h" +#include "../../Framework/StoneInitialization.h" // From Orthanc framework #include <Core/Logging.h> @@ -42,11 +41,12 @@ #include <Core/Images/ImageProcessing.h> #include <Core/Images/PngWriter.h> +#include <boost/ref.hpp> +#include <boost/make_shared.hpp> #include <SDL.h> + #include <stdio.h> -using namespace Orthanc; - namespace OrthancStone { const char* MeasureToolToString(size_t i) @@ -68,7 +68,12 @@ return descs[i]; } - Scene2DPtr TrackerSampleApp::GetScene() + boost::shared_ptr<Scene2D> TrackerSampleApp::GetScene() + { + return controller_->GetScene(); + } + + boost::shared_ptr<const Scene2D> TrackerSampleApp::GetScene() const { return controller_->GetScene(); } @@ -155,6 +160,71 @@ GetScene()->DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX); } + ScenePoint2D TrackerSampleApp::GetRandomPointInScene() const + { + unsigned int w = compositor_->GetCanvasWidth(); + LOG(TRACE) << "compositor_->GetCanvasWidth() = " << + compositor_->GetCanvasWidth(); + unsigned int h = compositor_->GetCanvasHeight(); + LOG(TRACE) << "compositor_->GetCanvasHeight() = " << + compositor_->GetCanvasHeight(); + + if ((w >= RAND_MAX) || (h >= RAND_MAX)) + LOG(WARNING) << "Canvas is too big : tools will not be randomly placed"; + + int x = rand() % w; + int y = rand() % h; + LOG(TRACE) << "random x = " << x << "random y = " << y; + + ScenePoint2D p = compositor_->GetPixelCenterCoordinates(x, y); + LOG(TRACE) << "--> p.GetX() = " << p.GetX() << " p.GetY() = " << p.GetY(); + + ScenePoint2D r = p.Apply(GetScene()->GetCanvasToSceneTransform()); + LOG(TRACE) << "--> r.GetX() = " << r.GetX() << " r.GetY() = " << r.GetY(); + return r; + } + + void TrackerSampleApp::CreateRandomMeasureTool() + { + static bool srandCalled = false; + if (!srandCalled) + { + srand(42); + srandCalled = true; + } + + int i = rand() % 2; + LOG(TRACE) << "random i = " << i; + switch (i) + { + case 0: + // line measure + { + boost::shared_ptr<CreateLineMeasureCommand> cmd = + boost::make_shared<CreateLineMeasureCommand>( + boost::ref(IObserver::GetBroker()), + controller_, + GetRandomPointInScene()); + cmd->SetEnd(GetRandomPointInScene()); + controller_->PushCommand(cmd); + } + break; + case 1: + // angle measure + { + boost::shared_ptr<CreateAngleMeasureCommand> cmd = + boost::make_shared<CreateAngleMeasureCommand>( + boost::ref(IObserver::GetBroker()), + controller_, + GetRandomPointInScene()); + cmd->SetCenter(GetRandomPointInScene()); + cmd->SetSide2End(GetRandomPointInScene()); + controller_->PushCommand(cmd); + } + break; + } + } + void TrackerSampleApp::HandleApplicationEvent( const SDL_Event & event) { @@ -166,8 +236,8 @@ const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); if (activeTracker_.get() == NULL && - SDL_SCANCODE_LCTRL < scancodeCount && - keyboardState[SDL_SCANCODE_LCTRL]) + SDL_SCANCODE_LALT < scancodeCount && + keyboardState[SDL_SCANCODE_LALT]) { // The "left-ctrl" key is down, while no tracker is present // Let's display the info text @@ -190,8 +260,8 @@ //LOG(TRACE) << "event.button.x = " << event.button.x << " " << // "event.button.y = " << event.button.y; - //LOG(TRACE) << "activeTracker_->PointerMove(e); " << - // e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY(); + LOG(TRACE) << "activeTracker_->PointerMove(e); " << + e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY(); activeTracker_->PointerMove(e); if (!activeTracker_->IsAlive()) @@ -251,11 +321,46 @@ } break; + case SDLK_m: + CreateRandomMeasureTool(); + break; case SDLK_s: controller_->FitContent(compositor_->GetCanvasWidth(), compositor_->GetCanvasHeight()); break; + case SDLK_z: + LOG(TRACE) << "SDLK_z has been pressed. event.key.keysym.mod == " << event.key.keysym.mod; + if (event.key.keysym.mod & KMOD_CTRL) + { + if (controller_->CanUndo()) + { + LOG(TRACE) << "Undoing..."; + controller_->Undo(); + } + else + { + LOG(WARNING) << "Nothing to undo!!!"; + } + } + break; + + case SDLK_y: + LOG(TRACE) << "SDLK_y has been pressed. event.key.keysym.mod == " << event.key.keysym.mod; + if (event.key.keysym.mod & KMOD_CTRL) + { + if (controller_->CanRedo()) + { + LOG(TRACE) << "Redoing..."; + controller_->Redo(); + } + else + { + LOG(WARNING) << "Nothing to redo!!!"; + } + } + break; + case SDLK_c: TakeScreenshot( "screenshot.png", @@ -276,18 +381,20 @@ DisplayInfoText(); } - FlexiblePointerTrackerPtr TrackerSampleApp::CreateSuitableTracker( + boost::shared_ptr<IFlexiblePointerTracker> TrackerSampleApp::CreateSuitableTracker( const SDL_Event & event, const PointerEvent & e) { + using namespace Orthanc; + switch (event.button.button) { case SDL_BUTTON_MIDDLE: - return FlexiblePointerTrackerPtr(new PanSceneTracker + return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker (controller_, e)); case SDL_BUTTON_RIGHT: - return FlexiblePointerTrackerPtr(new ZoomSceneTracker + return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker (controller_, e, compositor_->GetCanvasHeight())); case SDL_BUTTON_LEFT: @@ -300,7 +407,7 @@ // TODO: if there are conflicts, we should prefer a tracker that // pertains to the type of measuring tool currently selected (TBD?) - FlexiblePointerTrackerPtr hitTestTracker = TrackerHitTest(e); + boost::shared_ptr<IFlexiblePointerTracker> hitTestTracker = TrackerHitTest(e); if (hitTestTracker != NULL) { @@ -313,13 +420,13 @@ { case GuiTool_Rotate: //LOG(TRACE) << "Creating RotateSceneTracker"; - return FlexiblePointerTrackerPtr(new RotateSceneTracker( + return boost::shared_ptr<IFlexiblePointerTracker>(new RotateSceneTracker( controller_, e)); case GuiTool_Pan: - return FlexiblePointerTrackerPtr(new PanSceneTracker( + return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker( controller_, e)); case GuiTool_Zoom: - return FlexiblePointerTrackerPtr(new ZoomSceneTracker( + return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker( controller_, e, compositor_->GetCanvasHeight())); //case GuiTool_AngleMeasure: // return new AngleMeasureTracker(GetScene(), e); @@ -328,32 +435,34 @@ //case GuiTool_EllipseMeasure: // return new EllipseMeasureTracker(GetScene(), e); case GuiTool_LineMeasure: - return FlexiblePointerTrackerPtr(new CreateLineMeasureTracker( + return boost::shared_ptr<IFlexiblePointerTracker>(new CreateLineMeasureTracker( IObserver::GetBroker(), controller_, e)); case GuiTool_AngleMeasure: - return FlexiblePointerTrackerPtr(new CreateAngleMeasureTracker( + return boost::shared_ptr<IFlexiblePointerTracker>(new CreateAngleMeasureTracker( IObserver::GetBroker(), controller_, e)); case GuiTool_CircleMeasure: LOG(ERROR) << "Not implemented yet!"; - return FlexiblePointerTrackerPtr(); + return boost::shared_ptr<IFlexiblePointerTracker>(); case GuiTool_EllipseMeasure: LOG(ERROR) << "Not implemented yet!"; - return FlexiblePointerTrackerPtr(); + return boost::shared_ptr<IFlexiblePointerTracker>(); default: throw OrthancException(ErrorCode_InternalError, "Wrong tool!"); } } } default: - return FlexiblePointerTrackerPtr(); + return boost::shared_ptr<IFlexiblePointerTracker>(); } } TrackerSampleApp::TrackerSampleApp(MessageBroker& broker) : IObserver(broker) , currentTool_(GuiTool_Rotate) + , undoStack_(new UndoStack) { - controller_ = ViewportControllerPtr(new ViewportController(broker)); + controller_ = boost::shared_ptr<ViewportController>( + new ViewportController(undoStack_, broker)); controller_->RegisterObserverCallback( new Callable<TrackerSampleApp, ViewportController::SceneTransformChanged> @@ -427,14 +536,14 @@ chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5)); chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5)); chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5)); - layer->AddChain(chain, true); + layer->AddChain(chain, true, 255, 0, 0); chain.clear(); chain.push_back(ScenePoint2D(-5, -5)); chain.push_back(ScenePoint2D(5, -5)); chain.push_back(ScenePoint2D(5, 5)); chain.push_back(ScenePoint2D(-5, 5)); - layer->AddChain(chain, true); + layer->AddChain(chain, true, 0, 255, 0); double dy = 1.01; chain.clear(); @@ -442,9 +551,8 @@ chain.push_back(ScenePoint2D(4, -4 + dy)); chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy)); chain.push_back(ScenePoint2D(4, 2)); - layer->AddChain(chain, false); + layer->AddChain(chain, false, 0, 0, 255); - layer->SetColor(0, 255, 255); GetScene()->SetLayer(LINESET_1_ZINDEX, layer.release()); } @@ -485,10 +593,10 @@ } - FlexiblePointerTrackerPtr TrackerSampleApp::TrackerHitTest(const PointerEvent & e) + boost::shared_ptr<IFlexiblePointerTracker> TrackerSampleApp::TrackerHitTest(const PointerEvent & e) { - // std::vector<MeasureToolPtr> measureTools_; - return FlexiblePointerTrackerPtr(); + // std::vector<boost::shared_ptr<MeasureTool>> measureTools_; + return boost::shared_ptr<IFlexiblePointerTracker>(); } static void GLAPIENTRY
--- a/Samples/Sdl/TrackerSampleApp.h Wed Jun 19 17:36:33 2019 +0200 +++ b/Samples/Sdl/TrackerSampleApp.h Mon Jun 24 14:35:00 2019 +0200 @@ -18,15 +18,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ -#include <Framework/Scene2DViewport/PointerTypes.h> -#include <Framework/Messages/IObserver.h> - -#include <Framework/Scene2D/OpenGLCompositor.h> - -#include <Framework/Scene2DViewport/ViewportController.h> -#include <Framework/Scene2DViewport/IFlexiblePointerTracker.h> -#include <Framework/Scene2DViewport/MeasureTools.h> +#include "../../Framework/Messages/IObserver.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" +#include "../../Framework/Scene2DViewport/MeasureTool.h" +#include "../../Framework/Scene2DViewport/PredeclaredTypes.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" #include <SDL.h> @@ -54,6 +52,7 @@ static const unsigned int FONT_SIZE_1 = 24; class Scene2D; + class UndoStack; class TrackerSampleApp : public IObserver , public boost::enable_shared_from_this<TrackerSampleApp> @@ -66,7 +65,8 @@ void SetInfoDisplayMessage(std::string key, std::string value); void DisableTracker(); - Scene2DPtr GetScene(); + boost::shared_ptr<Scene2D> GetScene(); + boost::shared_ptr<const Scene2D> GetScene() const; void HandleApplicationEvent(const SDL_Event& event); @@ -79,11 +79,17 @@ private: void SelectNextTool(); - + void CreateRandomMeasureTool(); - FlexiblePointerTrackerPtr TrackerHitTest(const PointerEvent& e); + /** + This returns a random point in the canvas part of the scene, but in + scene coordinates + */ + ScenePoint2D GetRandomPointInScene() const; - FlexiblePointerTrackerPtr CreateSuitableTracker( + boost::shared_ptr<IFlexiblePointerTracker> TrackerHitTest(const PointerEvent& e); + + boost::shared_ptr<IFlexiblePointerTracker> CreateSuitableTracker( const SDL_Event& event, const PointerEvent& e); @@ -95,7 +101,7 @@ /** This adds the command at the top of the undo stack */ - void Commit(TrackerCommandPtr cmd); + void Commit(boost::shared_ptr<TrackerCommand> cmd); void Undo(); void Redo(); @@ -110,10 +116,10 @@ WARNING: the measuring tools do store a reference to the scene, and it paramount that the scene gets destroyed AFTER the measurement tools. */ - ViewportControllerPtr controller_; + boost::shared_ptr<ViewportController> controller_; std::map<std::string, std::string> infoTextMap_; - FlexiblePointerTrackerPtr activeTracker_; + boost::shared_ptr<IFlexiblePointerTracker> activeTracker_; //static const int LAYER_POSITION = 150; @@ -126,6 +132,7 @@ int FIXED_INFOTEXT_LAYER_ZINDEX; GuiTool currentTool_; + boost::shared_ptr<UndoStack> undoStack_; }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/BasicMPR.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,427 @@ +/** + * 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 "dev.h" + +#include <emscripten.h> + +#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" +#include "../../Framework/Oracle/SleepOracleCommand.h" +#include "../../Framework/Oracle/WebAssemblyOracle.h" +#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/Volumes/VolumeSceneLayerSource.h" + + +namespace OrthancStone +{ + class VolumeSlicerWidget : public IObserver + { + private: + OrthancStone::WebAssemblyViewport viewport_; + std::auto_ptr<VolumeSceneLayerSource> source_; + VolumeProjection projection_; + std::vector<CoordinateSystem3D> planes_; + size_t currentPlane_; + + void Handle(const DicomVolumeImage::GeometryReadyMessage& message) + { + LOG(INFO) << "Geometry is available"; + + const VolumeImageGeometry& geometry = message.GetOrigin().GetGeometry(); + + const unsigned int depth = geometry.GetProjectionDepth(projection_); + currentPlane_ = depth / 2; + + planes_.resize(depth); + + for (unsigned int z = 0; z < depth; z++) + { + planes_[z] = geometry.GetProjectionSlice(projection_, z); + } + + Refresh(); + + viewport_.FitContent(); + } + + public: + VolumeSlicerWidget(MessageBroker& broker, + const std::string& canvas, + VolumeProjection projection) : + IObserver(broker), + viewport_(broker, canvas), + projection_(projection), + currentPlane_(0) + { + } + + void UpdateSize() + { + viewport_.UpdateSize(); + } + + void SetSlicer(int layerDepth, + const boost::shared_ptr<IVolumeSlicer>& slicer, + IObservable& loader, + ILayerStyleConfigurator* configurator) + { + if (source_.get() != NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "Only one slicer can be registered"); + } + + loader.RegisterObserverCallback( + new Callable<VolumeSlicerWidget, DicomVolumeImage::GeometryReadyMessage> + (*this, &VolumeSlicerWidget::Handle)); + + source_.reset(new VolumeSceneLayerSource(viewport_.GetScene(), layerDepth, slicer)); + + if (configurator != NULL) + { + source_->SetConfigurator(configurator); + } + } + + void Refresh() + { + if (source_.get() != NULL && + currentPlane_ < planes_.size()) + { + source_->Update(planes_[currentPlane_]); + viewport_.Refresh(); + } + } + + size_t GetSlicesCount() const + { + return planes_.size(); + } + + void Scroll(int delta) + { + if (!planes_.empty()) + { + int tmp = static_cast<int>(currentPlane_) + delta; + unsigned int next; + + if (tmp < 0) + { + next = 0; + } + else if (tmp >= static_cast<int>(planes_.size())) + { + next = planes_.size() - 1; + } + else + { + next = static_cast<size_t>(tmp); + } + + if (next != currentPlane_) + { + currentPlane_ = next; + Refresh(); + } + } + } + }; +} + + + + +boost::shared_ptr<OrthancStone::DicomVolumeImage> ct_(new OrthancStone::DicomVolumeImage); + +boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> loader_; + +std::auto_ptr<OrthancStone::VolumeSlicerWidget> widget1_; +std::auto_ptr<OrthancStone::VolumeSlicerWidget> widget2_; +std::auto_ptr<OrthancStone::VolumeSlicerWidget> widget3_; + +OrthancStone::MessageBroker broker_; +OrthancStone::WebAssemblyOracle oracle_(broker_); + + +EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) +{ + try + { + if (widget1_.get() != NULL) + { + widget1_->UpdateSize(); + } + + if (widget2_.get() != NULL) + { + widget2_->UpdateSize(); + } + + if (widget3_.get() != NULL) + { + widget3_->UpdateSize(); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while updating canvas size: " << e.What(); + } + + return true; +} + + + + +EM_BOOL OnAnimationFrame(double time, void *userData) +{ + try + { + if (widget1_.get() != NULL) + { + widget1_->Refresh(); + } + + if (widget2_.get() != NULL) + { + widget2_->Refresh(); + } + + if (widget3_.get() != NULL) + { + widget3_->Refresh(); + } + + return true; + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception in the animation loop, stopping now: " << e.What(); + return false; + } +} + + +static bool ctrlDown_ = false; + + +EM_BOOL OnMouseWheel(int eventType, + const EmscriptenWheelEvent *wheelEvent, + void *userData) +{ + try + { + if (userData != NULL) + { + int delta = 0; + + if (wheelEvent->deltaY < 0) + { + delta = -1; + } + + if (wheelEvent->deltaY > 0) + { + delta = 1; + } + + OrthancStone::VolumeSlicerWidget& widget = + *reinterpret_cast<OrthancStone::VolumeSlicerWidget*>(userData); + + if (ctrlDown_) + { + delta *= static_cast<int>(widget.GetSlicesCount() / 10); + } + + widget.Scroll(delta); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception in the wheel event: " << e.What(); + } + + return true; +} + + +EM_BOOL OnKeyDown(int eventType, + const EmscriptenKeyboardEvent *keyEvent, + void *userData) +{ + ctrlDown_ = keyEvent->ctrlKey; + return false; +} + + +EM_BOOL OnKeyUp(int eventType, + const EmscriptenKeyboardEvent *keyEvent, + void *userData) +{ + ctrlDown_ = false; + return false; +} + + + + +namespace OrthancStone +{ + class TestSleep : public IObserver + { + private: + WebAssemblyOracle& oracle_; + + void Schedule() + { + oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(2000)); + } + + void Handle(const SleepOracleCommand::TimeoutMessage& message) + { + LOG(INFO) << "TIMEOUT"; + Schedule(); + } + + public: + TestSleep(MessageBroker& broker, + WebAssemblyOracle& oracle) : + IObserver(broker), + oracle_(oracle) + { + oracle.RegisterObserverCallback( + new Callable<TestSleep, SleepOracleCommand::TimeoutMessage> + (*this, &TestSleep::Handle)); + + LOG(INFO) << "STARTING"; + Schedule(); + } + }; + + //static TestSleep testSleep(broker_, oracle_); +} + + + +static std::map<std::string, std::string> arguments_; + +static bool GetArgument(std::string& value, + const std::string& key) +{ + std::map<std::string, std::string>::const_iterator found = arguments_.find(key); + + if (found == arguments_.end()) + { + return false; + } + else + { + value = found->second; + return true; + } +} + + +extern "C" +{ + int main(int argc, char const *argv[]) + { + OrthancStone::StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + // Orthanc::Logging::EnableTraceLevel(true); + EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded"));); + } + + EMSCRIPTEN_KEEPALIVE + void SetArgument(const char* key, const char* value) + { + // This is called for each GET argument (cf. "app.js") + LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]"; + arguments_[key] = value; + } + + EMSCRIPTEN_KEEPALIVE + void Initialize() + { + try + { + oracle_.SetOrthancRoot(".."); + + loader_.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct_, oracle_, oracle_)); + + widget1_.reset(new OrthancStone::VolumeSlicerWidget(broker_, "mycanvas1", OrthancStone::VolumeProjection_Axial)); + { + std::auto_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator); + style->SetLinearInterpolation(true); + style->SetWindowing(OrthancStone::ImageWindowing_Bone); + widget1_->SetSlicer(0, loader_, *loader_, style.release()); + } + widget1_->UpdateSize(); + + widget2_.reset(new OrthancStone::VolumeSlicerWidget(broker_, "mycanvas2", OrthancStone::VolumeProjection_Coronal)); + { + std::auto_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator); + style->SetLinearInterpolation(true); + style->SetWindowing(OrthancStone::ImageWindowing_Bone); + widget2_->SetSlicer(0, loader_, *loader_, style.release()); + } + widget2_->UpdateSize(); + + widget3_.reset(new OrthancStone::VolumeSlicerWidget(broker_, "mycanvas3", OrthancStone::VolumeProjection_Sagittal)); + { + std::auto_ptr<OrthancStone::GrayscaleStyleConfigurator> style(new OrthancStone::GrayscaleStyleConfigurator); + style->SetLinearInterpolation(true); + style->SetWindowing(OrthancStone::ImageWindowing_Bone); + widget3_->SetSlicer(0, loader_, *loader_, style.release()); + } + widget3_->UpdateSize(); + + emscripten_set_resize_callback("#window", NULL, false, OnWindowResize); + + emscripten_set_wheel_callback("mycanvas1", widget1_.get(), false, OnMouseWheel); + emscripten_set_wheel_callback("mycanvas2", widget2_.get(), false, OnMouseWheel); + emscripten_set_wheel_callback("mycanvas3", widget3_.get(), false, OnMouseWheel); + + emscripten_set_keydown_callback("#window", NULL, false, OnKeyDown); + emscripten_set_keyup_callback("#window", NULL, false, OnKeyUp); + + emscripten_request_animation_frame_loop(OnAnimationFrame, NULL); + + + std::string ct; + if (GetArgument(ct, "ct")) + { + //loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); + loader_->LoadSeries(ct); + } + else + { + LOG(ERROR) << "No Orthanc identifier for the CT series was provided"; + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception during Initialize(): " << e.What(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/BasicMPR.html Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,60 @@ +<!doctype html> +<html lang="en-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>Stone of Orthanc</title> + + <style> + html, body { + width: 100%; + height: 100%; + margin: 0px; + border: 0; + overflow: hidden; /* Disable scrollbars */ + display: block; /* No floating content on sides */ + } + + #mycanvas1 { + position:absolute; + left:0%; + top:0%; + background-color: red; + width: 50%; + height: 100%; + } + + #mycanvas2 { + position:absolute; + left:50%; + top:0%; + background-color: green; + width: 50%; + height: 50%; + } + + #mycanvas3 { + position:absolute; + left:50%; + top:50%; + background-color: blue; + width: 50%; + height: 50%; + } + </style> + </head> + <body> + <canvas id="mycanvas1" oncontextmenu="return false;"></canvas> + <canvas id="mycanvas2" oncontextmenu="return false;"></canvas> + <canvas id="mycanvas3" oncontextmenu="return false;"></canvas> + + <script type="text/javascript" src="app.js"></script> + <script type="text/javascript" async src="BasicMPR.js"></script> + </body> +</html>
--- a/Samples/WebAssembly/BasicScene.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/Samples/WebAssembly/BasicScene.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -19,32 +19,20 @@ **/ +#include "dev.h" #include <emscripten.h> #include <emscripten/html5.h> // From Stone -#include "../../Applications/Sdl/SdlOpenGLWindow.h" -#include "../../Framework/Scene2D/CairoCompositor.h" #include "../../Framework/Scene2D/ColorTextureSceneLayer.h" -#include "../../Framework/Scene2D/OpenGLCompositor.h" -#include "../../Framework/Scene2D/PanSceneTracker.h" -#include "../../Framework/Scene2D/RotateSceneTracker.h" -#include "../../Framework/Scene2D/Scene2D.h" -#include "../../Framework/Scene2D/ZoomSceneTracker.h" #include "../../Framework/StoneInitialization.h" -#include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h" // From Orthanc framework #include <Core/Images/Image.h> #include <Core/Logging.h> #include <Core/OrthancException.h> -#include <stdio.h> - -static const unsigned int FONT_SIZE = 32; - - void PrepareScene(OrthancStone::Scene2D& scene) { using namespace OrthancStone; @@ -108,14 +96,14 @@ chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5)); chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5)); chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5)); - layer->AddChain(chain, true); + layer->AddChain(chain, true, 255, 0, 0); chain.clear(); chain.push_back(ScenePoint2D(-5, -5)); chain.push_back(ScenePoint2D(5, -5)); chain.push_back(ScenePoint2D(5, 5)); chain.push_back(ScenePoint2D(-5, 5)); - layer->AddChain(chain, true); + layer->AddChain(chain, true, 0, 255, 0); double dy = 1.01; chain.clear(); @@ -123,9 +111,8 @@ chain.push_back(ScenePoint2D(4, -4 + dy)); chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy)); chain.push_back(ScenePoint2D(4, 2)); - layer->AddChain(chain, false); + layer->AddChain(chain, false, 0, 0, 255); - layer->SetColor(0,255, 255); scene.SetLayer(50, layer.release()); } @@ -139,234 +126,13 @@ } - - -namespace OrthancStone -{ - class WebAssemblyViewport : public boost::noncopyable - { - private: - OpenGL::WebAssemblyOpenGLContext context_; - Scene2D scene_; - OpenGLCompositor compositor_; - - void SetupEvents(const std::string& canvas); - - public: - WebAssemblyViewport(MessageBroker& broker, - const std::string& canvas) : - context_(canvas), - scene_(broker), - compositor_(context_, scene_) - { - compositor_.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, - FONT_SIZE, Orthanc::Encoding_Latin1); - SetupEvents(canvas); - } - - Scene2D& GetScene() - { - return scene_; - } - - void UpdateSize() - { - context_.UpdateSize(); - compositor_.UpdateSize(); - Refresh(); - } - - void Refresh() - { - compositor_.Refresh(); - } - - const std::string& GetCanvasIdentifier() const - { - return context_.GetCanvasIdentifier(); - } - - ScenePoint2D GetPixelCenterCoordinates(int x, int y) const - { - return compositor_.GetPixelCenterCoordinates(x, y); - } - - unsigned int GetCanvasWidth() const - { - return context_.GetCanvasWidth(); - } - - unsigned int GetCanvasHeight() const - { - return context_.GetCanvasHeight(); - } - }; - - - - class ActiveTracker : public boost::noncopyable - { - private: - std::auto_ptr<IPointerTracker> tracker_; - std::string canvasIdentifier_; - bool insideCanvas_; - - public: - ActiveTracker(IPointerTracker* tracker, - const WebAssemblyViewport& viewport) : - tracker_(tracker), - canvasIdentifier_(viewport.GetCanvasIdentifier()), - insideCanvas_(true) - { - if (tracker_.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - void Update(const PointerEvent& event) - { - tracker_->Update(event); - } - - void Release() - { - tracker_->Release(); - } - }; -} - - - -static OrthancStone::PointerEvent* ConvertMouseEvent(const EmscriptenMouseEvent& source, - OrthancStone::WebAssemblyViewport& viewport) -{ - std::auto_ptr<OrthancStone::PointerEvent> target(new OrthancStone::PointerEvent); - - target->AddPosition(viewport.GetPixelCenterCoordinates(source.targetX, source.targetY)); - target->SetAltModifier(source.altKey); - target->SetControlModifier(source.ctrlKey); - target->SetShiftModifier(source.shiftKey); - - return target.release(); -} - - -std::auto_ptr<OrthancStone::ActiveTracker> tracker_; - - -EM_BOOL OnMouseEvent(int eventType, - const EmscriptenMouseEvent *mouseEvent, - void *userData) -{ - if (mouseEvent != NULL && - userData != NULL) - { - OrthancStone::WebAssemblyViewport& viewport = - *reinterpret_cast<OrthancStone::WebAssemblyViewport*>(userData); - - switch (eventType) - { - case EMSCRIPTEN_EVENT_CLICK: - { - static unsigned int count = 0; - char buf[64]; - sprintf(buf, "click %d", count++); - - std::auto_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer); - layer->SetText(buf); - viewport.GetScene().SetLayer(100, layer.release()); - viewport.Refresh(); - break; - } - - case EMSCRIPTEN_EVENT_MOUSEDOWN: - { - std::auto_ptr<OrthancStone::IPointerTracker> t; - - { - std::auto_ptr<OrthancStone::PointerEvent> event(ConvertMouseEvent(*mouseEvent, viewport)); - - switch (mouseEvent->button) - { - case 0: // Left button - t.reset(new OrthancStone::RotateSceneTracker(viewport.GetScene(), *event)); - break; - - case 1: // Middle button - t.reset(new OrthancStone::PanSceneTracker(viewport.GetScene(), *event)); - break; - - case 2: // Right button - t.reset(new OrthancStone::ZoomSceneTracker - (viewport.GetScene(), *event, viewport.GetCanvasWidth())); - break; - - default: - break; - } - } - - if (t.get() != NULL) - { - tracker_.reset(new OrthancStone::ActiveTracker(t.release(), viewport)); - viewport.Refresh(); - } - - break; - } - - case EMSCRIPTEN_EVENT_MOUSEMOVE: - if (tracker_.get() != NULL) - { - std::auto_ptr<OrthancStone::PointerEvent> event(ConvertMouseEvent(*mouseEvent, viewport)); - tracker_->Update(*event); - viewport.Refresh(); - } - break; - - case EMSCRIPTEN_EVENT_MOUSEUP: - if (tracker_.get() != NULL) - { - tracker_->Release(); - viewport.Refresh(); - tracker_.reset(); - } - break; - - default: - break; - } - } - - return true; -} - - -void OrthancStone::WebAssemblyViewport::SetupEvents(const std::string& canvas) -{ - if (0) - { - emscripten_set_click_callback(canvas.c_str(), this, false, OnMouseEvent); - } - else - { - emscripten_set_mousedown_callback(canvas.c_str(), this, false, OnMouseEvent); - emscripten_set_mousemove_callback(canvas.c_str(), this, false, OnMouseEvent); - emscripten_set_mouseup_callback(canvas.c_str(), this, false, OnMouseEvent); - } -} - - - - std::auto_ptr<OrthancStone::WebAssemblyViewport> viewport1_; std::auto_ptr<OrthancStone::WebAssemblyViewport> viewport2_; std::auto_ptr<OrthancStone::WebAssemblyViewport> viewport3_; OrthancStone::MessageBroker broker_; - -EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) +EM_BOOL OnWindowResize( + int eventType, const EmscriptenUiEvent *uiEvent, void *userData) { if (viewport1_.get() != NULL) { @@ -386,28 +152,31 @@ return true; } - - extern "C" { int main(int argc, char const *argv[]) { OrthancStone::StoneInitialize(); + // Orthanc::Logging::EnableInfoLevel(true); + // Orthanc::Logging::EnableTraceLevel(true); EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded"));); } EMSCRIPTEN_KEEPALIVE void Initialize() { - viewport1_.reset(new OrthancStone::WebAssemblyViewport(broker_, "mycanvas1")); + viewport1_.reset( + new OrthancStone::WebAssemblyViewport(broker_, "mycanvas1")); PrepareScene(viewport1_->GetScene()); viewport1_->UpdateSize(); - viewport2_.reset(new OrthancStone::WebAssemblyViewport(broker_, "mycanvas2")); + viewport2_.reset( + new OrthancStone::WebAssemblyViewport(broker_, "mycanvas2")); PrepareScene(viewport2_->GetScene()); viewport2_->UpdateSize(); - viewport3_.reset(new OrthancStone::WebAssemblyViewport(broker_, "mycanvas3")); + viewport3_.reset( + new OrthancStone::WebAssemblyViewport(broker_, "mycanvas3")); PrepareScene(viewport3_->GetScene()); viewport3_->UpdateSize();
--- a/Samples/WebAssembly/CMakeLists.txt Wed Jun 19 17:36:33 2019 +0200 +++ b/Samples/WebAssembly/CMakeLists.txt Mon Jun 24 14:35:00 2019 +0200 @@ -13,6 +13,7 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXIT_RUNTIME=1") ##################################################################### @@ -59,6 +60,10 @@ include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + ##################################################################### ## Build the samples @@ -68,22 +73,43 @@ ${ORTHANC_STONE_SOURCES} ) -add_executable(BasicScene - BasicScene.cpp - ) + +if (ON) + add_executable(BasicScene + BasicScene.cpp + ) + + target_link_libraries(BasicScene OrthancStone) + + install( + TARGETS BasicScene + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} + ) +endif() -target_link_libraries(BasicScene OrthancStone) + +if (ON) + add_executable(BasicMPR + BasicMPR.cpp + ) -install( - TARGETS BasicScene - RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} - ) + target_link_libraries(BasicMPR OrthancStone) + + install( + TARGETS BasicMPR + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} + ) +endif() + install( FILES + ${CMAKE_CURRENT_BINARY_DIR}/BasicMPR.wasm ${CMAKE_CURRENT_BINARY_DIR}/BasicScene.wasm + ${CMAKE_SOURCE_DIR}/BasicMPR.html ${CMAKE_SOURCE_DIR}/BasicScene.html ${CMAKE_SOURCE_DIR}/Configuration.json + ${CMAKE_SOURCE_DIR}/app.js ${CMAKE_SOURCE_DIR}/index.html DESTINATION ${CMAKE_INSTALL_PREFIX} )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/ConfigurationLocalSJO.json Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,20 @@ +{ + "Plugins": [ + "/home/jodogne/Subversion/orthanc-webviewer/r/libOrthancWebViewer.so", + "/home/jodogne/Subversion/orthanc/r/libServeFolders.so" + ], + "StorageDirectory" : "/tmp/orthanc-db", + "IndexDirectory" : "/tmp/orthanc-db", + "RemoteAccessAllowed" : true, + "AuthenticationEnabled" : false, + "ServeFolders" : { + "AllowCache" : false, + "GenerateETag" : true, + "Folders" : { + "/stone" : "/tmp/stone" + } + }, + "WebViewer" : { + "CachePath" : "/tmp/orthanc-db/WebViewerCache" + } +}
--- a/Samples/WebAssembly/NOTES.txt Wed Jun 19 17:36:33 2019 +0200 +++ b/Samples/WebAssembly/NOTES.txt Mon Jun 24 14:35:00 2019 +0200 @@ -1,4 +1,65 @@ +Docker SJO +========== + $ source ~/Downloads/emsdk/emsdk_env.sh $ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON .. -DCMAKE_INSTALL_PREFIX=/tmp/stone $ ninja install -$ sudo docker run -p 4242:4242 -p 8042:8042 --rm -v /tmp/stone:/root/stone:ro jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose +$ docker run -p 4242:4242 -p 8042:8042 --rm -v /tmp/stone:/root/stone:ro -v /tmp/stone-db/:/var/lib/orthanc/db/ jodogne/orthanc-plugins:latest /root/stone/Configuration.json --verbose + +WARNING: This won't work using "orthanc-plugins:1.5.6", as support for +PAM is mandatatory in "/instances/.../image-uint16". + + +Docker BGO +========== + +On Ubuntu WSL +------------- +. ~/apps/emsdk/emsdk_env.sh +cd /mnt/c/osi/dev/ +mkdir -p build_stone_newsamples_wasm_wsl +mkdir -p build_install_stone_newsamples_wasm_wsl +cd build_stone_newsamples_wasm_wsl +cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON /mnt/c/osi/dev/orthanc-stone/Samples/WebAssembly -DCMAKE_INSTALL_PREFIX=/mnt/c/osi/dev/build_install_stone_newsamples_wasm_wsl +ninja install + +Then, on Windows +----------------- +docker run -p 4242:4242 -p 8042:8042 --rm -v "C:/osi/dev/build_install_stone_newsamples_wasm_wsl:/root/stone:ro" jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose + +# WAIT A COUPLE OF SECS +# if the archive has NOT already been unzipped, unzip it +# upload dicom files to running orthanc + +cd C:\osi\dev\twiga-orthanc-viewer\demo\dicomfiles +if (-not (test-path RTVIEWER-c8febcc6-eb9e22a4-130f208c-e0a6a4cd-4d432c57)) { unzip RTVIEWER-c8febcc6-eb9e22a4-130f208c-e0a6a4cd-4d432c57.zip} +ImportDicomFiles.ps1 127.0.0.1 8042 .\RTVIEWER-c8febcc6-eb9e22a4-130f208c-e0a6a4cd-4d432c57\ + +--> localhost:8042 --> Plugins --> serve-folders --> stone --> ... + +Local BGO +========== + +. ~/apps/emsdk/emsdk_env.sh +cd /mnt/c/osi/dev/ +mkdir -p build_stone_newsamples_wasm_wsl +mkdir -p build_install_stone_newsamples_wasm_wsl +cd build_stone_newsamples_wasm_wsl +cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON /mnt/c/osi/dev/orthanc-stone/Samples/WebAssembly -DCMAKE_INSTALL_PREFIX=/mnt/c/osi/dev/build_install_stone_newsamples_wasm_wsl + + + +TODO: Orthanc.exe + + +Local SJO +========== + +$ source ~/Downloads/emsdk/emsdk_env.sh +$ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON .. -DCMAKE_INSTALL_PREFIX=/tmp/stone +$ ninja install + +$ make -C ~/Subversion/orthanc/r -j4 +$ make -C ~/Subversion/orthanc-webviewer/r -j4 +$ ~/Subversion/orthanc/r/Orthanc ../ConfigurationLocalSJO.json +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/app.js Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,33 @@ +/** + * This is a generic bootstrap code that is shared by all the Stone + * sample applications. + **/ + +// Check support for WebAssembly +if (!('WebAssembly' in window)) { + alert('Sorry, your browser does not support WebAssembly :('); +} else { + + // Wait for the module to be loaded (the event "WebAssemblyLoaded" + // must be emitted by the "main" function) + window.addEventListener('WebAssemblyLoaded', function() { + + // Loop over the GET arguments + var parameters = window.location.search.substr(1); + if (parameters != null && parameters != '') { + var tokens = parameters.split('&'); + for (var i = 0; i < tokens.length; i++) { + var arg = tokens[i].split('='); + if (arg.length == 2) { + + // Send each GET argument to WebAssembly + Module.ccall('SetArgument', null, [ 'string', 'string' ], + [ arg[0], decodeURIComponent(arg[1]) ]); + } + } + } + + // Inform the WebAssembly module that it can start + Module.ccall('Initialize', null, null, null); + }); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/dev.h Mon Jun 24 14:35:00 2019 +0200 @@ -0,0 +1,271 @@ +/** + * 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 "../../Framework/OpenGL/WebAssemblyOpenGLContext.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" + +#include <Core/OrthancException.h> + +#include <emscripten/html5.h> + +static const unsigned int FONT_SIZE = 32; + +namespace OrthancStone +{ + class WebAssemblyViewport : public boost::noncopyable + { + private: + // the construction order is important because compositor_ + // will hold a reference to the scene that belong to the + // controller_ object + OpenGL::WebAssemblyOpenGLContext context_; + boost::shared_ptr<ViewportController> controller_; + OpenGLCompositor compositor_; + + void SetupEvents(const std::string& canvas); + + public: + WebAssemblyViewport(MessageBroker& broker, + const std::string& canvas) : + context_(canvas), + controller_(new ViewportController(broker)), + compositor_(context_, *controller_->GetScene()) + { + compositor_.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE, Orthanc::Encoding_Latin1); + SetupEvents(canvas); + } + + Scene2D& GetScene() + { + return *controller_->GetScene(); + } + + const boost::shared_ptr<ViewportController>& GetController() + { + return controller_; + } + + void UpdateSize() + { + context_.UpdateSize(); + compositor_.UpdateSize(); + Refresh(); + } + + void Refresh() + { + compositor_.Refresh(); + } + + void FitContent() + { + GetScene().FitContent(context_.GetCanvasWidth(), context_.GetCanvasHeight()); + } + + const std::string& GetCanvasIdentifier() const + { + return context_.GetCanvasIdentifier(); + } + + ScenePoint2D GetPixelCenterCoordinates(int x, int y) const + { + return compositor_.GetPixelCenterCoordinates(x, y); + } + + unsigned int GetCanvasWidth() const + { + return context_.GetCanvasWidth(); + } + + unsigned int GetCanvasHeight() const + { + return context_.GetCanvasHeight(); + } + }; + + class ActiveTracker : public boost::noncopyable + { + private: + boost::shared_ptr<IFlexiblePointerTracker> tracker_; + std::string canvasIdentifier_; + bool insideCanvas_; + + public: + ActiveTracker(const boost::shared_ptr<IFlexiblePointerTracker>& tracker, + const WebAssemblyViewport& viewport) : + tracker_(tracker), + canvasIdentifier_(viewport.GetCanvasIdentifier()), + insideCanvas_(true) + { + if (tracker_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + bool IsAlive() const + { + return tracker_->IsAlive(); + } + + void PointerMove(const PointerEvent& event) + { + tracker_->PointerMove(event); + } + + void PointerUp(const PointerEvent& event) + { + tracker_->PointerUp(event); + } + }; +} + +static OrthancStone::PointerEvent* ConvertMouseEvent( + const EmscriptenMouseEvent& source, + OrthancStone::WebAssemblyViewport& viewport) +{ + std::auto_ptr<OrthancStone::PointerEvent> target( + new OrthancStone::PointerEvent); + + target->AddPosition(viewport.GetPixelCenterCoordinates( + source.targetX, source.targetY)); + target->SetAltModifier(source.altKey); + target->SetControlModifier(source.ctrlKey); + target->SetShiftModifier(source.shiftKey); + + return target.release(); +} + +std::auto_ptr<OrthancStone::ActiveTracker> tracker_; + +EM_BOOL OnMouseEvent(int eventType, + const EmscriptenMouseEvent *mouseEvent, + void *userData) +{ + if (mouseEvent != NULL && + userData != NULL) + { + OrthancStone::WebAssemblyViewport& viewport = + *reinterpret_cast<OrthancStone::WebAssemblyViewport*>(userData); + + switch (eventType) + { + case EMSCRIPTEN_EVENT_CLICK: + { + static unsigned int count = 0; + char buf[64]; + sprintf(buf, "click %d", count++); + + std::auto_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer); + layer->SetText(buf); + viewport.GetScene().SetLayer(100, layer.release()); + viewport.Refresh(); + break; + } + + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t; + + { + std::auto_ptr<OrthancStone::PointerEvent> event( + ConvertMouseEvent(*mouseEvent, viewport)); + + switch (mouseEvent->button) + { + case 0: // Left button + emscripten_console_log("Creating RotateSceneTracker"); + t.reset(new OrthancStone::RotateSceneTracker( + viewport.GetController(), *event)); + break; + + case 1: // Middle button + emscripten_console_log("Creating PanSceneTracker"); + LOG(INFO) << "Creating PanSceneTracker" ; + t.reset(new OrthancStone::PanSceneTracker( + viewport.GetController(), *event)); + break; + + case 2: // Right button + emscripten_console_log("Creating ZoomSceneTracker"); + t.reset(new OrthancStone::ZoomSceneTracker( + viewport.GetController(), *event, viewport.GetCanvasWidth())); + break; + + default: + break; + } + } + + if (t.get() != NULL) + { + tracker_.reset( + new OrthancStone::ActiveTracker(t, viewport)); + viewport.Refresh(); + } + + break; + } + + case EMSCRIPTEN_EVENT_MOUSEMOVE: + if (tracker_.get() != NULL) + { + std::auto_ptr<OrthancStone::PointerEvent> event( + ConvertMouseEvent(*mouseEvent, viewport)); + tracker_->PointerMove(*event); + viewport.Refresh(); + } + break; + + case EMSCRIPTEN_EVENT_MOUSEUP: + if (tracker_.get() != NULL) + { + std::auto_ptr<OrthancStone::PointerEvent> event( + ConvertMouseEvent(*mouseEvent, viewport)); + tracker_->PointerUp(*event); + viewport.Refresh(); + if (!tracker_->IsAlive()) + tracker_.reset(); + } + break; + + default: + break; + } + } + + return true; +} + + +void OrthancStone::WebAssemblyViewport::SetupEvents(const std::string& canvas) +{ + emscripten_set_mousedown_callback(canvas.c_str(), this, false, OnMouseEvent); + emscripten_set_mousemove_callback(canvas.c_str(), this, false, OnMouseEvent); + emscripten_set_mouseup_callback(canvas.c_str(), this, false, OnMouseEvent); +}
--- a/Samples/WebAssembly/index.html Wed Jun 19 17:36:33 2019 +0200 +++ b/Samples/WebAssembly/index.html Mon Jun 24 14:35:00 2019 +0200 @@ -10,6 +10,7 @@ <h1>Available samples</h1> <ul> <li><a href="BasicScene.html">Basic scene</a></li> + <li><a href="BasicMPR.html">Basic MPR display</a></li> </ul> </body> </html>
--- a/UnitTestsSources/UnitTestsMain.cpp Wed Jun 19 17:36:33 2019 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Mon Jun 24 14:35:00 2019 +0200 @@ -19,14 +19,14 @@ **/ -#include "../Framework/dev.h" #include "gtest/gtest.h" -#include "../Framework/Layers/FrameRenderer.h" -#include "../Framework/Toolbox/DownloadStack.h" +#include "../Framework/Deprecated/Layers/FrameRenderer.h" +#include "../Framework/Deprecated/Toolbox/DownloadStack.h" +#include "../Framework/Deprecated/Toolbox/MessagingToolbox.h" +#include "../Framework/Deprecated/Toolbox/OrthancSlicesLoader.h" #include "../Framework/Toolbox/FiniteProjectiveCamera.h" -#include "../Framework/Toolbox/MessagingToolbox.h" -#include "../Framework/Toolbox/OrthancSlicesLoader.h" +#include "../Framework/Toolbox/GeometryToolbox.h" #include "../Framework/Volumes/ImageBuffer3D.h" #include "../Platforms/Generic/OracleWebService.h" @@ -42,97 +42,6 @@ #include <boost/math/special_functions/round.hpp> -#if 0 -namespace OrthancStone -{ - class Tata : public OrthancSlicesLoader::ICallback - { - public: - virtual void NotifyGeometryReady(const OrthancSlicesLoader& loader) - { - printf(">> %d\n", (int) loader.GetSliceCount()); - - for (size_t i = 0; i < loader.GetSliceCount(); i++) - { - const_cast<OrthancSlicesLoader&>(loader).ScheduleLoadSliceImage(i, SliceImageQuality_FullPng); - } - } - - virtual void NotifyGeometryError(const OrthancSlicesLoader& loader) - { - printf("Error\n"); - } - - virtual void NotifySliceImageReady(const OrthancSlicesLoader& loader, - unsigned int sliceIndex, - const Slice& slice, - std::auto_ptr<Orthanc::ImageAccessor>& image, - SliceImageQuality quality) - { - std::auto_ptr<Orthanc::ImageAccessor> tmp(image); - printf("Slice OK %dx%d\n", tmp->GetWidth(), tmp->GetHeight()); - } - - virtual void NotifySliceImageError(const OrthancSlicesLoader& loader, - unsigned int sliceIndex, - const Slice& slice, - SliceImageQuality quality) - { - printf("ERROR 2\n"); - } - }; -} - - -TEST(Toto, DISABLED_Tutu) -{ - OrthancStone::Oracle oracle(4); - oracle.Start(); - - Orthanc::WebServiceParameters web; - //OrthancStone::OrthancAsynchronousWebService orthanc(web, 4); - OrthancStone::OracleWebService orthanc(oracle, web); - //orthanc.Start(); - - OrthancStone::Tata tata; - OrthancStone::OrthancSlicesLoader loader(tata, orthanc); - loader.ScheduleLoadSeries("c1c4cb95-05e3bd11-8da9f5bb-87278f71-0b2b43f5"); - //loader.ScheduleLoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); - - //loader.ScheduleLoadInstance("19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5", 0); - - /*printf(">> %d\n", loader.GetSliceCount()); - loader.ScheduleLoadSliceImage(31);*/ - - boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); - - //orthanc.Stop(); - oracle.Stop(); -} - - -TEST(Toto, Tata) -{ - OrthancStone::Oracle oracle(4); - oracle.Start(); - - Orthanc::WebServiceParameters web; - OrthancStone::OracleWebService orthanc(oracle, web); - OrthancStone::OrthancVolumeImage volume(orthanc, true); - - //volume.ScheduleLoadInstance("19816330-cb02e1cf-df3a8fe8-bf510623-ccefe9f5", 0); - //volume.ScheduleLoadSeries("318603c5-03e8cffc-a82b6ee1-3ccd3c1e-18d7e3bb"); // COMUNIX PET - //volume.ScheduleLoadSeries("7124dba7-09803f33-98b73826-33f14632-ea842d29"); // COMUNIX CT - //volume.ScheduleLoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // Delphine sagital - volume.ScheduleLoadSeries("6f1b492a-e181e200-44e51840-ef8db55e-af529ab6"); // Delphine ax 2.5 - - boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); - - oracle.Stop(); -} -#endif - - TEST(GeometryToolbox, Interpolation) { using namespace OrthancStone::GeometryToolbox; @@ -727,7 +636,7 @@ { Json::Value response; std::string source = "{\"command\":\"panel:takeDarkImage\",\"commandType\":\"simple\",\"args\":{}}"; - ASSERT_TRUE(OrthancStone::MessagingToolbox::ParseJson(response, source.c_str(), source.size())); + ASSERT_TRUE(Deprecated::MessagingToolbox::ParseJson(response, source.c_str(), source.size())); } TEST(VolumeImageGeometry, Basic) @@ -790,18 +699,22 @@ OrthancStone::VolumeProjection projection = (OrthancStone::VolumeProjection) p; const OrthancStone::CoordinateSystem3D& s = g.GetProjectionGeometry(projection); + ASSERT_THROW(g.GetProjectionSlice(projection, g.GetProjectionDepth(projection)), Orthanc::OrthancException); + for (unsigned int i = 0; i < g.GetProjectionDepth(projection); i++) { - OrthancStone::CoordinateSystem3D plane( - s.GetOrigin() + static_cast<double>(i) * s.GetNormal() * g.GetVoxelDimensions(projection)[2], - s.GetAxisX(), - s.GetAxisY()); + OrthancStone::CoordinateSystem3D plane = g.GetProjectionSlice(projection, i); + + ASSERT_TRUE(IsEqualVector(plane.GetOrigin(), s.GetOrigin() + static_cast<double>(i) * + s.GetNormal() * g.GetVoxelDimensions(projection)[2])); + ASSERT_TRUE(IsEqualVector(plane.GetAxisX(), s.GetAxisX())); + ASSERT_TRUE(IsEqualVector(plane.GetAxisY(), s.GetAxisY())); unsigned int slice; OrthancStone::VolumeProjection q; ASSERT_TRUE(g.DetectSlice(q, slice, plane)); ASSERT_EQ(projection, q); - ASSERT_EQ(i, slice); + ASSERT_EQ(i, slice); } } }