# HG changeset patch # User Benjamin Golinvaux # Date 1587557147 -7200 # Node ID f4a06ad1580be20bb47050d6977e7a89751b3394 # Parent 4431ffdcc2a415e02e0ba31a5a1ef42e26584e65# Parent 6ea4062c1a0d4887348fbb9e5288ec041854d77c Branch broker is now the new default diff -r 4431ffdcc2a4 -r f4a06ad1580b .hgignore --- a/.hgignore Mon Apr 20 18:26:32 2020 +0200 +++ b/.hgignore Wed Apr 22 14:05:47 2020 +0200 @@ -5,15 +5,15 @@ .vs/ .vscode/ Applications/Qt/archive/ -Applications/Samples/ThirdPartyDownloads/ -Applications/Samples/build-wasm/ -Applications/Samples/build-web/ -Applications/Samples/node_modules/ -Applications/Samples/rt-viewer-demo/ThirdPartyDownloads/ -Applications/Samples/rt-viewer-demo/build-sdl-msvc15/ -Applications/Samples/rt-viewer-demo/build-tsc-output/ -Applications/Samples/rt-viewer-demo/build-wasm/ -Applications/Samples/rt-viewer-demo/build-web/ +Applications/Samples/Deprecated/ThirdPartyDownloads/ +Applications/Samples/Deprecated/build-wasm/ +Applications/Samples/Deprecated/build-web/ +Applications/Samples/Deprecated/node_modules/ +Applications/Samples/Deprecated/rt-viewer-demo/ThirdPartyDownloads/ +Applications/Samples/Deprecated/rt-viewer-demo/build-sdl-msvc15/ +Applications/Samples/Deprecated/rt-viewer-demo/build-tsc-output/ +Applications/Samples/Deprecated/rt-viewer-demo/build-wasm/ +Applications/Samples/Deprecated/rt-viewer-demo/build-web/ Applications/build-* CMakeLists.txt.user Platforms/Generic/ThirdPartyDownloads/ @@ -31,12 +31,16 @@ Resources/CommandTool/protoc-tests/generated_js/ Resources/CommandTool/protoc-tests/generated_ts/ Resources/CommandTool/protoc-tests/node_modules/ -Samples/Sdl/ThirdPartyDownloads/ -Samples/Sdl/CMakeLists.txt.orig -Samples/Qt/ThirdPartyDownloads/ - -Samples/WebAssembly/build/ -Samples/WebAssembly/ThirdPartyDownloads/ -Samples/WebAssembly/installDir/ +Samples/WebAssembly/*/ThirdPartyDownloads/ +Samples/WebAssembly/*/out +Samples/Sdl/*/ThirdPartyDownloads/ +Samples/Sdl/*/out +Samples/Deprecated/Sdl/ThirdPartyDownloads/ +Samples/Deprecated/Sdl/CMakeLists.txt.orig +Samples/Deprecated/Qt/ThirdPartyDownloads/ +Samples/Deprecated/WebAssembly/build/ +Samples/Deprecated/WebAssembly/ThirdPartyDownloads/ +Samples/Deprecated/WebAssembly/installDir/ node_modules/ + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Generic/GuiAdapter.cpp --- a/Applications/Generic/GuiAdapter.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Generic/GuiAdapter.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -31,7 +31,7 @@ #endif #if ORTHANC_ENABLE_THREADS == 1 -# include "../../Framework/Messages/LockingEmitter.h" +# include "../../Framework/Deprecated/Messages/LockingEmitter.h" #endif #include @@ -47,6 +47,15 @@ return os; } + std::ostream& operator<<( + std::ostream& os, const GuiAdapterMouseEvent& event) + { + os << "targetX: " << event.targetX << " targetY: " << event.targetY << " button: " << event.button + << "ctrlKey: " << event.ctrlKey << "shiftKey: " << event.shiftKey << "altKey: " << event.altKey; + + return os; + } + #if ORTHANC_ENABLE_WASM == 1 void GuiAdapter::Run(GuiAdapterRunFunc /*func*/, void* /*cookie*/) { @@ -147,7 +156,7 @@ template struct FuncAdapterPayload { - std::string canvasId; + std::string canvasCssSelector; void* userData; GenericFunc callback; }; @@ -158,7 +167,6 @@ EM_BOOL OnEventAdapterFunc( int eventType, const EmscriptenEvent* emEvent, void* userData) { - // userData is OnMouseWheelFuncAdapterPayload FuncAdapterPayload* payload = reinterpret_cast*>(userData); @@ -170,7 +178,7 @@ GuiAdapterEvent guiEvent; ConvertFromPlatform(guiEvent, eventType, *emEvent); - bool ret = (*(payload->callback))(payload->canvasId, &guiEvent, payload->userData); + bool ret = (*(payload->callback))(payload->canvasCssSelector, &guiEvent, payload->userData); return static_cast(ret); } @@ -186,7 +194,7 @@ GuiAdapterEvent guiEvent; ConvertFromPlatform(guiEvent, *wheelEvent); - bool ret = (*(payload->callback))(payload->canvasId, &guiEvent, payload->userData); + bool ret = (*(payload->callback))(payload->canvasCssSelector, &guiEvent, payload->userData); return static_cast(ret); } @@ -202,6 +210,76 @@ return static_cast(ret); } + /* + + Explanation + =========== + + - in "older" Emscripten, where DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR doesn't exist or is set to 0, + the following strings need to be used to register events: + - for canvas, the canvas DOM id. In case of ", the string needs + to be "mycanvas" + - for the window (for key events), the string needs to be "#window" + - in newer Emscripten where DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR==1 (or maybe is not there anymore, in the + future as of 2020-04-20) + - for canvas, the canvas DOM id. In case of ", the string needs + to be "#mycanvas" (notice the "number sign", aka "hash", NOT AKA "sharp", as can be read on https://en.wikipedia.org/wiki/Number_sign) + - for the window (for key events), the string needs to be EMSCRIPTEN_EVENT_TARGET_WINDOW. I do not mean + "EMSCRIPTEN_EVENT_TARGET_WINDOW", but the #define EMSCRIPTEN_EVENT_TARGET_WINDOW ((const char*)2) that + can be found in emscripten/html5.h + + The code below converts the input canvasId (as in the old emscripten) to the emscripten-compliant one, with the + following compile condition : #if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 + + If the DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR build parameter disappears, you might want to refactor this code + or continue to pass the DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR compile macro (which is different from the CMake + variable) + + What we are doing below: + - in older Emscripten, the registration functions will receive "mycanvas" and "#window" and the callbacks will receive + the same std::string in their payload ("mycanvas" and "#window") + + - in newer Emscripten, the registration functions will receive "#mycanvas" and EMSCRIPTEN_EVENT_TARGET_WINDOW, but + the callbacks will receive "#mycanvas" and "#window" (since it is not possible to store the EMSCRIPTEN_EVENT_TARGET_WINDOW + magic value in an std::string, while we still want the callback to be able to change its behavior according to the + target element. + + */ + + void convertElementTarget(const char*& outCanvasCssSelectorSz, std::string& outCanvasCssSelector, const std::string& canvasId) + { + // only "#window" can start with a # + if (canvasId[0] == '#') + { + ORTHANC_ASSERT(canvasId == "#window"); + } +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 + if (canvasId == "#window") + { + // we store this in the payload so that the callback can + outCanvasCssSelector = "#window"; + outCanvasCssSelectorSz = EMSCRIPTEN_EVENT_TARGET_WINDOW; + } + else + { + outCanvasCssSelector = "#" + canvasId; + outCanvasCssSelectorSz = outCanvasCssSelector.c_str(); + } +#else + if (canvasId == "#window") + { + // we store this in the payload so that the callback can + outCanvasCssSelector = "#window"; + outCanvasCssSelectorSz = outCanvasCssSelector.c_str();; + } + else + { + outCanvasCssSelector = canvasId; + outCanvasCssSelectorSz = outCanvasCssSelector.c_str();; + } +#endif + } + // resize: (const char* target, void* userData, EM_BOOL useCapture, em_ui_callback_func callback) template< typename GenericFunc, @@ -212,24 +290,26 @@ EmscriptenSetCallbackFunc emFunc, std::string canvasId, void* userData, bool capture, GenericFunc func) { - // TODO: write RemoveCallback with an int id that gets returned from - // here - FuncAdapterPayload* payload = - new FuncAdapterPayload(); - std::unique_ptr > payloadP(payload); - payload->canvasId = canvasId; + std::string canvasCssSelector; + const char* canvasCssSelectorSz = NULL; + convertElementTarget(canvasCssSelectorSz, canvasCssSelector, canvasId); + + // TODO: write RemoveCallback with an int id that gets returned from here + + // create userdata payload + std::unique_ptr > payload(new FuncAdapterPayload()); + payload->canvasCssSelector = canvasCssSelector; payload->callback = func; payload->userData = userData; - void* userDataRaw = reinterpret_cast(payload); - // LOG(INFO) << "SetCallback -- userDataRaw: " << userDataRaw << - // " payload: " << payload << " payload->userData: " << payload->userData; + void* userDataRaw = reinterpret_cast(payload.release()); + + // call the registration function (*emFunc)( - canvasId.c_str(), + canvasCssSelectorSz, userDataRaw, static_cast(capture), &OnEventAdapterFunc, EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD); - payloadP.release(); } template< @@ -241,15 +321,22 @@ EmscriptenSetCallbackFunc emFunc, std::string canvasId, void* userData, bool capture, GenericFunc func) { - std::unique_ptr > payload( - new FuncAdapterPayload() - ); - payload->canvasId = canvasId; + std::string canvasCssSelector; + const char* canvasCssSelectorSz = NULL; + convertElementTarget(canvasCssSelectorSz, canvasCssSelector, canvasId); + + // TODO: write RemoveCallback with an int id that gets returned from here + + // create userdata payload + std::unique_ptr > payload(new FuncAdapterPayload()); + payload->canvasCssSelector = canvasCssSelector; payload->callback = func; payload->userData = userData; void* userDataRaw = reinterpret_cast(payload.release()); + + // call the registration function (*emFunc)( - canvasId.c_str(), + canvasCssSelectorSz, userDataRaw, static_cast(capture), &OnEventAdapterFunc2, @@ -263,11 +350,10 @@ EmscriptenSetCallbackFunc emFunc, void* userData, GenericFunc func) { - // LOG(ERROR) << "SetAnimationFrameCallback !!!!!! (RequestAnimationFrame)"; std::unique_ptr > payload( new FuncAdapterPayload() ); - payload->canvasId = "UNDEFINED"; + payload->canvasCssSelector = "UNDEFINED"; payload->callback = func; payload->userData = userData; void* userDataRaw = reinterpret_cast(payload.release()); @@ -358,6 +444,8 @@ func); } +#if 0 + // useless under Wasm where canvas resize is handled automatically void GuiAdapter::SetResizeCallback( std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func) { @@ -368,13 +456,11 @@ capture, func); } +#endif void GuiAdapter::RequestAnimationFrame( OnAnimationFrameFunc func, void* userData) { - // LOG(ERROR) << "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"; - // LOG(ERROR) << "RequestAnimationFrame"; - // LOG(ERROR) << "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"; SetAnimationFrameCallback( &emscripten_request_animation_frame_loop, userData, @@ -393,10 +479,11 @@ emscripten_set_keyup_callback(canvasId.c_str(), userData, static_cast(capture), func); } - void GuiAdapter::SetResizeCallback(std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func) - { - emscripten_set_resize_callback(canvasId.c_str(), userData, static_cast(capture), func); - } + // handled from within WebAssemblyViewport + //void GuiAdapter::SetResizeCallback(std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func) + //{ + // emscripten_set_resize_callback(canvasId.c_str(), userData, static_cast(capture), func); + //} void GuiAdapter::RequestAnimationFrame(OnAnimationFrameFunc func, void* userData) { @@ -516,13 +603,11 @@ dest.altKey = false; } - - // SDL ONLY - void GuiAdapter::SetResizeCallback( - std::string canvasId, void* userData, bool capture, OnWindowResizeFunc func) + void GuiAdapter::SetSdlResizeCallback( + std::string canvasId, void* userData, bool capture, OnSdlWindowResizeFunc func) { - resizeHandlers_.push_back(EventHandlerData(canvasId, func, userData)); + resizeHandlers_.push_back(EventHandlerData(canvasId, func, userData)); } // SDL ONLY @@ -575,6 +660,13 @@ } // SDL ONLY + void GuiAdapter::SetGenericSdlEventCallback( + std::string canvasId, void* userData, bool capture, OnSdlEventCallback func) + { + sdlEventHandlers_.push_back(EventHandlerData(canvasId, func, userData)); + } + + // SDL ONLY void GuiAdapter::OnAnimationFrame() { std::vector disabledAnimationHandlers; @@ -596,19 +688,91 @@ } // SDL ONLY - void GuiAdapter::OnResize() + void GuiAdapter::OnResize(unsigned int width, unsigned int height) { for (size_t i = 0; i < resizeHandlers_.size(); i++) { (*(resizeHandlers_[i].func))( - resizeHandlers_[i].canvasName, 0, resizeHandlers_[i].userData); + resizeHandlers_[i].canvasName, NULL, width, height, resizeHandlers_[i].userData); + } + } + + + + void GuiAdapter::OnSdlGenericEvent(const SDL_Event& sdlEvent) + { + // Events related to a window are only sent to the related canvas + // User events are sent to everyone (we can't filter them here) + + /* + SDL_WindowEvent SDL_WINDOWEVENT + SDL_KeyboardEvent SDL_KEYDOWN + SDL_KEYUP + SDL_TextEditingEvent SDL_TEXTEDITING + SDL_TextInputEvent SDL_TEXTINPUT + SDL_MouseMotionEvent SDL_MOUSEMOTION + SDL_MouseButtonEvent SDL_MOUSEBUTTONDOWN + SDL_MOUSEBUTTONUP + SDL_MouseWheelEvent SDL_MOUSEWHEEL + SDL_UserEvent SDL_USEREVENT through ::SDL_LASTEVENT-1 + */ + + // if this string is left empty, it means the message will be sent to + // all widgets. + // otherwise, it contains the originating message window title + + std::string windowTitle; + uint32_t windowId = 0; + + if (sdlEvent.type == SDL_WINDOWEVENT) + windowId = sdlEvent.window.windowID; + else if (sdlEvent.type == SDL_KEYDOWN || sdlEvent.type == SDL_KEYUP) + windowId = sdlEvent.key.windowID; + else if (sdlEvent.type == SDL_TEXTEDITING) + windowId = sdlEvent.edit.windowID; + else if (sdlEvent.type == SDL_TEXTINPUT) + windowId = sdlEvent.text.windowID; + else if (sdlEvent.type == SDL_MOUSEMOTION) + windowId = sdlEvent.motion.windowID; + else if (sdlEvent.type == SDL_MOUSEBUTTONDOWN || sdlEvent.type == SDL_MOUSEBUTTONUP) + windowId = sdlEvent.button.windowID; + else if (sdlEvent.type == SDL_MOUSEWHEEL) + windowId = sdlEvent.wheel.windowID; + else if (sdlEvent.type >= SDL_USEREVENT && sdlEvent.type <= (SDL_LASTEVENT-1)) + windowId = sdlEvent.user.windowID; + + if (windowId != 0) + { + 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!"); + windowTitle = windowTitleSz; + ORTHANC_ASSERT(windowTitle != "", "Window ID \"" << windowId << "\" has an empty window title!"); + } + + for (size_t i = 0; i < sdlEventHandlers_.size(); i++) + { + // normally, the handlers return a bool indicating whether they + // have handled the event or not, but we don't really care about this + std::string& canvasName = sdlEventHandlers_[i].canvasName; + + bool sendEvent = true; + + if (windowTitle != "" && (canvasName != windowTitle)) + sendEvent = false; + + if (sendEvent) + { + OnSdlEventCallback func = sdlEventHandlers_[i].func; + (*func)(canvasName, sdlEvent, sdlEventHandlers_[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); @@ -726,17 +890,6 @@ ORTHANC_ASSERT(false, "Wrong event.type: " << event.type << " in GuiAdapter::OnMouseEvent(...)"); break; } - - ////boost::shared_ptr GetWidgetFromWindowId(); - //boost::shared_ptr 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-> } } @@ -771,6 +924,31 @@ } # endif +#if 0 + // TODO: remove this when generic sdl event handlers are implemented in + // the DoseView + // SDL ONLY + bool GuiAdapter::IsSdlViewPortRefreshEvent(const SDL_Event& event) const + { + SDL_Window* sdlWindow = SDL_GetWindowFromID(event.window.windowID); + + ORTHANC_ASSERT(sdlWindow != NULL, "Window ID \"" << event.window.windowID << "\" is not a valid SDL window ID!"); + + const char* windowTitleSz = SDL_GetWindowTitle(sdlWindow); + + // now we need to find the DoseView from from the canvas name! + // (and retrieve the SdlViewport) + boost::shared_ptr foundWidget; + VisitWidgets([&foundWidget, windowTitleSz](auto widget) + { + if (widget->GetCanvasIdentifier() == std::string(windowTitleSz)) + foundWidget = widget; + }); + ORTHANC_ASSERT(foundWidget, "The window named: \"" << windowTitleSz << "\" was not found in the registered widgets!"); + return foundWidget->GetSdlViewport().IsRefreshEvent(event); + } +#endif + // SDL ONLY void GuiAdapter::Run(GuiAdapterRunFunc func, void* cookie) { @@ -789,141 +967,171 @@ while (!stop) { { - LockingEmitter::WriterLock lock(lockingEmitter_); + // TODO: lock all viewports here! (use a scoped object) if(func != NULL) (*func)(cookie); OnAnimationFrame(); // in SDL we must call it } - SDL_Event event; - - while (!stop && SDL_PollEvent(&event)) + while (!stop) { - 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); + std::vector sdlEvents; + std::map userEventsMap; + + SDL_Event sdlEvent; - 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) + // FIRST: collect all pending events + while (SDL_PollEvent(&sdlEvent) != 0) { -#if 0 - tracker.reset(); -#endif - OnResize(); + if ( (sdlEvent.type >= SDL_USEREVENT) && + (sdlEvent.type <= SDL_USEREVENT) ) + { + // we don't want to have multiple events with the same event.type + userEventsMap[sdlEvent.type] = sdlEvent; + } + else + { + sdlEvents.push_back(sdlEvent); + } } - 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; - // This commented out code was used to debug the context - // loss/restoring code (2019-08-10) - // case SDLK_k: - // { - // SDL_Window* window = SDL_GetWindowFromID(event.window.windowID); - // std::string windowTitle(SDL_GetWindowTitle(window)); - // Debug_SetContextToBeKilled(windowTitle); - // } - // break; - // case SDLK_l: - // { - // SDL_Window* window = SDL_GetWindowFromID(event.window.windowID); - // std::string windowTitle(SDL_GetWindowTitle(window)); - // Debug_SetContextToBeRestored(windowTitle); - // } - // break; + // SECOND: collect all user events + for (std::map::const_iterator it = userEventsMap.begin(); it != userEventsMap.end(); ++it) + sdlEvents.push_back(it->second); + + // now process the events + for (std::vector::const_iterator it = sdlEvents.begin(); it != sdlEvents.end(); ++it) + { + const SDL_Event& sdlEvent = *it; + // TODO: lock all viewports here! (use a scoped object) - case SDLK_q: + if (sdlEvent.type == SDL_QUIT) + { + // TODO: call exit callbacks here stop = true; break; + } + else if ((sdlEvent.type == SDL_MOUSEMOTION) || + (sdlEvent.type == SDL_MOUSEBUTTONDOWN) || + (sdlEvent.type == SDL_MOUSEBUTTONUP)) + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + bool ctrlPressed(false); + bool shiftPressed(false); + bool altPressed(false); - default: - GuiAdapterKeyboardEvent dest; - ConvertFromPlatform(dest, event); - OnKeyboardEvent(event.window.windowID, dest); - break; + 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, sdlEvent); + OnMouseEvent(sdlEvent.window.windowID, dest); + #if 0 + // for reference, how to create trackers + if (tracker) + { + PointerEvent e; + e.AddPosition(compositor.GetPixelCenterCoordinates( + sdlEvent.button.x, sdlEvent.button.y)); + tracker->PointerMove(e); + } + #endif } + else if (sdlEvent.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, sdlEvent); + OnMouseWheelEvent(sdlEvent.window.windowID, dest); + + //KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); + + //int x, y; + //SDL_GetMouseState(&x, &y); + + //if (sdlEvent.wheel.y > 0) + //{ + // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Up, x, y, modifiers); + //} + //else if (sdlEvent.wheel.y < 0) + //{ + // locker.GetCentralViewport().MouseWheel(MouseWheelDirection_Down, x, y, modifiers); + //} + } + else if (sdlEvent.type == SDL_WINDOWEVENT && + (sdlEvent.window.event == SDL_WINDOWEVENT_RESIZED || + sdlEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) + { + #if 0 + tracker.reset(); + #endif + OnResize(sdlEvent.window.data1, sdlEvent.window.data2); + } + else if (sdlEvent.type == SDL_KEYDOWN && sdlEvent.key.repeat == 0 /* Ignore key bounce */) + { + switch (sdlEvent.key.keysym.sym) + { + case SDLK_f: + // window.GetWindow().ToggleMaximize(); //TODO: move to particular handler + break; + + // This commented out code was used to debug the context + // loss/restoring code (2019-08-10) + // case SDLK_k: + // { + // SDL_Window* window = SDL_GetWindowFromID(sdlEvent.window.windowID); + // std::string windowTitle(SDL_GetWindowTitle(window)); + // Debug_SetContextToBeKilled(windowTitle); + // } + // break; + // case SDLK_l: + // { + // SDL_Window* window = SDL_GetWindowFromID(sdlEvent.window.windowID); + // std::string windowTitle(SDL_GetWindowTitle(window)); + // Debug_SetContextToBeRestored(windowTitle); + // } + // break; + + case SDLK_q: + stop = true; + break; + + default: + GuiAdapterKeyboardEvent dest; + ConvertFromPlatform(dest, sdlEvent); + OnKeyboardEvent(sdlEvent.window.windowID, dest); + break; + } + } + + OnSdlGenericEvent(sdlEvent); } - // HandleApplicationEvent(controller, compositor, event, tracker); } SDL_Delay(1); diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Generic/GuiAdapter.h --- a/Applications/Generic/GuiAdapter.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Generic/GuiAdapter.h Wed Apr 22 14:05:47 2020 +0200 @@ -43,8 +43,8 @@ #include "../../Framework/StoneException.h" -#if ORTHANC_ENABLE_THREADS != 1 -# include "../../Framework/Messages/LockingEmitter.h" +#if ORTHANC_ENABLE_THREADS == 1 +# include "../../Framework/Deprecated/Messages/LockingEmitter.h" #endif #include @@ -53,6 +53,11 @@ namespace OrthancStone { +#if ORTHANC_ENABLE_SDL == 1 + class SdlViewport; +#endif + +#if 0 /** This interface is used to store the widgets that are controlled by the @@ -64,8 +69,17 @@ { public: virtual ~IGuiAdapterWidget() {} + +#if #if ORTHANC_ENABLE_SDL == 1 + /** + Returns the SdlViewport that this widget contains. If the underlying + viewport type is *not* SDL, then an error is returned. + */ + virtual SdlViewport& GetSdlViewport() = 0; +#endif + }; - }; +#endif enum GuiAdapterMouseButtonType { @@ -95,16 +109,24 @@ 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 (*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); + +#if ORTHANC_ENABLE_SDL == 1 + typedef bool (*OnSdlEventCallback) (std::string canvasId, const SDL_Event& sdlEvent, void* userData); - typedef bool (*OnAnimationFrameFunc)(double time, void* userData); - typedef bool (*OnWindowResizeFunc)(std::string canvasId, const GuiAdapterUiEvent* uiEvent, void* userData); + typedef bool (*OnSdlWindowResizeFunc)(std::string canvasId, + const GuiAdapterUiEvent* uiEvent, + unsigned int width, + unsigned int height, + void* userData); + + +#endif #else @@ -180,6 +202,7 @@ }; std::ostream& operator<<(std::ostream& os, const GuiAdapterKeyboardEvent& event); + std::ostream& operator<<(std::ostream& os, const GuiAdapterMouseEvent& event); /* Mousedown event trigger when either the left or right (or middle) mouse is pressed @@ -224,11 +247,7 @@ class GuiAdapter { public: -#if ORTHANC_ENABLE_THREADS == 1 - GuiAdapter(LockingEmitter& lockingEmitter) : lockingEmitter_(lockingEmitter) -#else GuiAdapter() -#endif { static int instanceCount = 0; ORTHANC_ASSERT(instanceCount == 0); @@ -236,14 +255,14 @@ } /** - emscripten_set_resize_callback("#window", NULL, false, OnWindowResize); + emscripten_set_resize_callback("EMSCRIPTEN_EVENT_TARGET_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_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_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyDown); ---> NO! + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyUp); emscripten_request_animation_frame_loop(OnAnimationFrame, NULL); @@ -252,27 +271,33 @@ */ - void SetMouseDownCallback (std::string canvasId, void* userData, bool capture, OnMouseEventFunc func); - void SetMouseDblClickCallback (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 SetMouseDownCallback (std::string canvasId, void* userData, bool capture, OnMouseEventFunc func); + void SetMouseDblClickCallback (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 ORTHANC_ENABLE_SDL == 1 + + void SetGenericSdlEventCallback (std::string canvasId, void* userData, bool capture, OnSdlEventCallback func); + + typedef bool (*OnSdlEventCallback) (std::string canvasId, const SDL_Event& sdlEvent, void* userData); + + // if you pass "#window", then any Window resize will trigger the callback + // (this special string is converted to EMSCRIPTEN_EVENT_TARGET_WINDOW in DOM, when DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1) + void SetSdlResizeCallback(std::string canvasId, + void* userData, + bool capture, + OnSdlWindowResizeFunc func); +#endif 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 @@ -280,26 +305,14 @@ */ void Run(GuiAdapterRunFunc func = NULL, void* cookie = NULL); -#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 +#if ORTHANC_ENABLE_SDL == 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. + Gives observers a chance to react based on generic event handlers. This + is used, for instance, when the viewport lock interface is invalidated. */ - LockingEmitter& lockingEmitter_; + void OnSdlGenericEvent(const SDL_Event& sdlEvent); #endif /** @@ -311,7 +324,7 @@ std::vector > animationFrameHandlers_; - void OnResize(); + void OnResize(unsigned int width, unsigned int height); #if ORTHANC_ENABLE_SDL == 1 template @@ -328,14 +341,15 @@ Func func; void* userData; }; - std::vector > resizeHandlers_; - std::vector > mouseDownHandlers_; - std::vector > mouseDblCickHandlers_; - std::vector > mouseMoveHandlers_; - std::vector > mouseUpHandlers_; - std::vector > mouseWheelHandlers_; - std::vector > keyDownHandlers_; - std::vector > keyUpHandlers_; + std::vector > resizeHandlers_; + std::vector > mouseDownHandlers_; + std::vector > mouseDblCickHandlers_; + std::vector > mouseMoveHandlers_; + std::vector > mouseUpHandlers_; + std::vector > mouseWheelHandlers_; + std::vector > keyDownHandlers_; + std::vector > keyUpHandlers_; + std::vector > sdlEventHandlers_; /** This executes all the registered headers if needed (in wasm, the browser @@ -350,8 +364,6 @@ */ void OnMouseWheelEvent(uint32_t windowID, const GuiAdapterWheelEvent& event); - boost::shared_ptr GetWidgetFromWindowId(); - #endif /** diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Generic/NativeStoneApplicationContext.cpp --- a/Applications/Generic/NativeStoneApplicationContext.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Generic/NativeStoneApplicationContext.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -24,10 +24,10 @@ namespace OrthancStone { - Deprecated::IWidget& NativeStoneApplicationContext::GlobalMutexLocker::SetCentralWidget(Deprecated::IWidget* widget) + void NativeStoneApplicationContext::GlobalMutexLocker::SetCentralWidget( + boost::shared_ptr widget) { that_.centralViewport_.SetCentralWidget(widget); - return *widget; } @@ -45,9 +45,7 @@ } - NativeStoneApplicationContext::NativeStoneApplicationContext(MessageBroker& broker) : - StoneApplicationContext(broker), - centralViewport_(broker), + NativeStoneApplicationContext::NativeStoneApplicationContext() : stopped_(true), updateDelayInMs_(100) // By default, 100ms between each refresh of the content { diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Generic/NativeStoneApplicationContext.h --- a/Applications/Generic/NativeStoneApplicationContext.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Generic/NativeStoneApplicationContext.h Wed Apr 22 14:05:47 2020 +0200 @@ -56,7 +56,7 @@ { } - Deprecated::IWidget& SetCentralWidget(Deprecated::IWidget* widget); // Takes ownership + void SetCentralWidget(boost::shared_ptr widget); Deprecated::IViewport& GetCentralViewport() { @@ -67,14 +67,9 @@ { that_.updateDelayInMs_ = delayInMs; } - - MessageBroker& GetMessageBroker() - { - return that_.GetMessageBroker(); - } }; - NativeStoneApplicationContext(MessageBroker& broker); + NativeStoneApplicationContext(); void Start(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Generic/NativeStoneApplicationRunner.cpp --- a/Applications/Generic/NativeStoneApplicationRunner.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Generic/NativeStoneApplicationRunner.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -97,7 +97,7 @@ DeclareCommandLineOptions(options); // application specific options - application_.DeclareStartupOptions(options); + application_->DeclareStartupOptions(options); boost::program_options::variables_map parameters; bool error = false; @@ -197,7 +197,7 @@ LogStatusBar statusBar; - NativeStoneApplicationContext context(broker_); + NativeStoneApplicationContext context; { // use multiple threads to execute asynchronous tasks like @@ -206,24 +206,23 @@ oracle.Start(); { - Deprecated::OracleWebService webService( - broker_, oracle, webServiceParameters, context); - + boost::shared_ptr webService + (new Deprecated::OracleWebService(oracle, webServiceParameters, context)); context.SetWebService(webService); context.SetOrthancBaseUrl(webServiceParameters.GetUrl()); - Deprecated::OracleDelayedCallExecutor delayedExecutor(broker_, oracle, context); + Deprecated::OracleDelayedCallExecutor delayedExecutor(oracle, context); context.SetDelayedCallExecutor(delayedExecutor); - application_.Initialize(&context, statusBar, parameters); + application_->Initialize(&context, statusBar, parameters); { NativeStoneApplicationContext::GlobalMutexLocker locker(context); - locker.SetCentralWidget(application_.GetCentralWidget()); + locker.SetCentralWidget(application_->GetCentralWidget()); locker.GetCentralViewport().SetStatusBar(statusBar); } - std::string title = application_.GetTitle(); + std::string title = application_->GetTitle(); if (title.empty()) { title = "Stone of Orthanc"; @@ -244,7 +243,7 @@ } LOG(WARNING) << "The application is stopping"; - application_.Finalize(); + application_->Finalize(); } catch (Orthanc::OrthancException& e) { diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Generic/NativeStoneApplicationRunner.h --- a/Applications/Generic/NativeStoneApplicationRunner.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Generic/NativeStoneApplicationRunner.h Wed Apr 22 14:05:47 2020 +0200 @@ -34,14 +34,11 @@ class NativeStoneApplicationRunner { protected: - MessageBroker& broker_; - IStoneApplication& application_; + boost::shared_ptr application_; + public: - - NativeStoneApplicationRunner(MessageBroker& broker, - IStoneApplication& application) - : broker_(broker), - application_(application) + NativeStoneApplicationRunner(boost::shared_ptr application) + : application_(application) { } int Execute(int argc, diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/IStoneApplication.h --- a/Applications/IStoneApplication.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/IStoneApplication.h Wed Apr 22 14:05:47 2020 +0200 @@ -64,7 +64,11 @@ #endif virtual std::string GetTitle() const = 0; - virtual Deprecated::IWidget* GetCentralWidget() = 0; + + virtual void SetCentralWidget(boost::shared_ptr widget) = 0; + + virtual boost::shared_ptr GetCentralWidget() = 0; + virtual void Finalize() = 0; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Qt/QCairoWidget.cpp diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/BasicPetCtFusionApplication.h --- a/Applications/Samples/BasicPetCtFusionApplication.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,202 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "SampleInteractor.h" - -#include - -namespace OrthancStone -{ - namespace Samples - { - class BasicPetCtFusionApplication : public SampleApplicationBase - { - private: - class Interactor : public SampleInteractor - { - public: - static void SetStyle(LayeredSceneWidget& widget, - bool ct, - bool pet) - { - if (ct) - { - RenderStyle style; - style.windowing_ = ImageWindowing_Bone; - widget.SetLayerStyle(0, style); - } - else - { - RenderStyle style; - style.visible_ = false; - widget.SetLayerStyle(0, style); - } - - if (ct && pet) - { - RenderStyle style; - style.applyLut_ = true; - style.alpha_ = 0.5; - widget.SetLayerStyle(1, style); - } - else if (pet) - { - RenderStyle style; - style.applyLut_ = true; - widget.SetLayerStyle(1, style); - } - else - { - RenderStyle style; - style.visible_ = false; - widget.SetLayerStyle(1, style); - } - } - - - static bool IsVisible(LayeredSceneWidget& widget, - size_t layer) - { - RenderStyle style = widget.GetLayerStyle(layer); - return style.visible_; - } - - - static void ToggleInterpolation(LayeredSceneWidget& widget, - size_t layer) - { - RenderStyle style = widget.GetLayerStyle(layer); - - if (style.interpolation_ == ImageInterpolation_Bilinear) - { - style.interpolation_ = ImageInterpolation_Nearest; - } - else - { - style.interpolation_ = ImageInterpolation_Bilinear; - } - - widget.SetLayerStyle(layer, style); - } - - - Interactor(VolumeImage& volume, - VolumeProjection projection, - bool reverse) : - SampleInteractor(volume, projection, reverse) - { - } - - - virtual void KeyPressed(WorldSceneWidget& widget, - char key, - KeyboardModifiers modifiers, - IStatusBar* statusBar) - { - LayeredSceneWidget& layered = dynamic_cast(widget); - - switch (key) - { - case 'c': - // Toggle the visibility of the CT layer - SetStyle(layered, !IsVisible(layered, 0), IsVisible(layered, 1)); - break; - - case 'p': - // Toggle the visibility of the PET layer - SetStyle(layered, IsVisible(layered, 0), !IsVisible(layered, 1)); - break; - - case 'i': - { - // Toggle on/off the interpolation - ToggleInterpolation(layered, 0); - ToggleInterpolation(layered, 1); - break; - } - - default: - break; - } - } - }; - - - public: - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) - { - boost::program_options::options_description generic("Sample options"); - generic.add_options() - ("ct", boost::program_options::value(), - "Orthanc ID of the CT series") - ("pet", boost::program_options::value(), - "Orthanc ID of the PET series") - ("threads", boost::program_options::value()->default_value(3), - "Number of download threads for the CT series") - ; - - options.add(generic); - } - - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - using namespace OrthancStone; - - if (parameters.count("ct") != 1 || - parameters.count("pet") != 1) - { - LOG(ERROR) << "The series ID is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - std::string ct = parameters["ct"].as(); - std::string pet = parameters["pet"].as(); - unsigned int threads = parameters["threads"].as(); - - VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads); - VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1); - - // Take the PET volume as the reference for the slices - std::unique_ptr interactor(new Interactor(petVolume, VolumeProjection_Axial, false /* don't reverse normal */)); - - std::unique_ptr widget(new LayeredSceneWidget); - widget->AddLayer(new VolumeImage::LayerFactory(ctVolume)); - widget->AddLayer(new VolumeImage::LayerFactory(petVolume)); - widget->SetSlice(interactor->GetCursor().GetCurrentSlice()); - widget->SetInteractor(*interactor); - - Interactor::SetStyle(*widget, true, true); // Initially, show both CT and PET layers - - context.AddInteractor(interactor.release()); - context.SetCentralWidget(widget.release()); - - statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode"); - statusBar.SetMessage("Use the key \"c\" to show/hide the CT layer"); - statusBar.SetMessage("Use the key \"p\" to show/hide the PET layer"); - statusBar.SetMessage("Use the key \"i\" to toggle the smoothing of the images"); - } - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/CMakeLists.txt --- a/Applications/Samples/CMakeLists.txt Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,292 +0,0 @@ -# Usage (Linux): -# to build the WASM samples -# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc -DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON -# to build the Qt samples - -cmake_minimum_required(VERSION 2.8.3) -project(OrthancStone) - -include(../../Resources/CMake/OrthancStoneParameters.cmake) - -set(ENABLE_STONE_DEPRECATED ON) # Need deprecated classes for these samples -set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON) - -include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) -DownloadPackage( - "a24b8136b8f3bb93f166baf97d9328de" - "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" - "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") - -set(ORTHANC_STONE_APPLICATION_RESOURCES - UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf - ) - -if (OPENSSL_NO_CAPIENG) -add_definitions(-DOPENSSL_NO_CAPIENG=1) -endif() - - -# the following block has been borrowed from orthanc/**/Compiler.cmake -if (MSVC_MULTIPLE_PROCESSES) -# "If you omit the processMax argument in the /MP option, the -# compiler obtains the number of effective processors from the -# operating system, and then creates one process per effective -# processor" -# https://blog.kitware.com/cmake-building-with-all-your-cores/ -# https://docs.microsoft.com/en-us/cpp/build/reference/mp-build-with-multiple-processes -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") -endif() - -#set(ENABLE_DCMTK ON) - -set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") -set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") -set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") - -if (ENABLE_WASM) - ##################################################################### - ## Configuration of the Emscripten compiler for WebAssembly target - ##################################################################### - - set(WASM_FLAGS "-s WASM=1") - set(WASM_FLAGS "${WASM_FLAGS} -s STRICT=1") # drops support for all deprecated build options - set(WASM_FLAGS "${WASM_FLAGS} -s FILESYSTEM=1") # if we don't include it, gen_uuid.c fails to build because srand, getpid(), ... are not defined - set(WASM_FLAGS "${WASM_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0") # actually enable exception catching - set(WASM_FLAGS "${WASM_FLAGS} -s ERROR_ON_MISSING_LIBRARIES=1") - - if (CMAKE_BUILD_TYPE MATCHES DEBUG) - set(WASM_FLAGS "${WASM_FLAGS} -g4") # generate debug information - set(WASM_FLAGS "${WASM_FLAGS} -s ASSERTIONS=2") # more runtime checks - else() - set(WASM_FLAGS "${WASM_FLAGS} -Os") # optimize for web (speed and size) - endif() - - set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module") - - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") - - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${WASM_FLAGS}") # not always clear which flags are for the compiler and which one are for the linker -> pass them all to the linker too - # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"'") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_STACK=128000000") - - add_definitions(-DORTHANC_ENABLE_WASM=1) - set(ORTHANC_SANDBOXED ON) - -elseif (ENABLE_QT OR ENABLE_SDL) - - set(ENABLE_NATIVE ON) - set(ORTHANC_SANDBOXED OFF) - set(ENABLE_CRYPTO_OPTIONS ON) - set(ENABLE_GOOGLE_TEST ON) - set(ENABLE_WEB_CLIENT ON) - -else() - set(ENABLE_NATIVE ON) - set(ENABLE_OPENGL OFF) - -endif() - - -##################################################################### -## Configuration for Orthanc -##################################################################### - -# include(../../Resources/CMake/Version.cmake) - -if (ORTHANC_STONE_VERSION STREQUAL "mainline") - set(ORTHANC_FRAMEWORK_VERSION "mainline") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") -else() - set(ORTHANC_FRAMEWORK_VERSION "1.4.1") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") -endif() - -set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") -set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") -set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") - -add_definitions( - -DORTHANC_ENABLE_LOGGING_PLUGIN=0 - ) - - -##################################################################### -## Build a static library containing the Orthanc Stone framework -##################################################################### - - -LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) - -include(../../Resources/CMake/OrthancStoneConfiguration.cmake) - -add_library(OrthancStone STATIC - ${ORTHANC_STONE_SOURCES} - ) - -##################################################################### -## Build all the sample applications -##################################################################### - -include_directories(${ORTHANC_STONE_ROOT}) - -# files common to all samples -list(APPEND SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h - ) - -if (ENABLE_QT) - list(APPEND SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleQtApplicationRunner.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.cpp - ) - - ORTHANC_QT_WRAP_UI(SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.ui - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.ui - ) - - ORTHANC_QT_WRAP_CPP(SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Qt/QCairoWidget.h - ${ORTHANC_STONE_ROOT}/Applications/Qt/QStoneMainWindow.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.h - ) -endif() - -if (ENABLE_NATIVE) - list(APPEND SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainNative.cpp - ) - -elseif (ENABLE_WASM) - - list(APPEND SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainWasm.cpp - ${STONE_WASM_SOURCES} - ) -endif() - - -macro(BuildSingleFileSample Target Header Sample) - add_executable(${Target} - ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header} - ${SAMPLE_APPLICATIONS_SOURCES} - ) - set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) - target_link_libraries(${Target} OrthancStone) -endmacro() - - -if (ENABLE_SDL) - #BuildSingleFileSample(OrthancStoneEmpty EmptyApplication.h 1) - #BuildSingleFileSample(OrthancStoneTestPattern TestPatternApplication.h 2) - BuildSingleFileSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) - #BuildSingleFileSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) - #BuildSingleFileSample(OrthancStoneBasicPetCtFusion 5) - #BuildSingleFileSample(OrthancStoneSynchronizedSeries 6) - #BuildSingleFileSample(OrthancStoneLayoutPetCtFusion 7) - BuildSingleFileSample(OrthancStoneSimpleViewerSingleFile SimpleViewerApplicationSingleFile.h 8) # we keep that one just as a sample before we convert another sample to this pattern - BuildSingleFileSample(OrthancStoneSingleFrameEditor SingleFrameEditorApplication.h 9) -endif() - -##### SimpleViewer sample (Qt and WASM only) ####### - -if (ENABLE_QT OR ENABLE_WASM) - - if (ENABLE_QT) - list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/mainQt.cpp - ) - - ORTHANC_QT_WRAP_UI(SIMPLE_VIEWER_APPLICATION_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui - ) - - ORTHANC_QT_WRAP_CPP(SIMPLE_VIEWER_APPLICATION_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.h - ) - -elseif (ENABLE_WASM) - list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.h - ${STONE_WASM_SOURCES} - ) - endif() - - add_executable(OrthancStoneSimpleViewer - ${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/SimpleViewerApplication.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.h - ${SIMPLE_VIEWER_APPLICATION_SOURCES} - ) - target_link_libraries(OrthancStoneSimpleViewer OrthancStone) - - BuildSingleFileSample(OrthancStoneSingleFrameEditor SingleFrameEditorApplication.h 9) -endif() - -##################################################################### -## Build the unit tests -##################################################################### - -if (ENABLE_NATIVE) - add_executable(UnitTests - ${GOOGLE_TEST_SOURCES} - ${ORTHANC_STONE_ROOT}/UnitTestsSources/GenericToolboxTests.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/ImageToolboxTests.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/PixelTestPatternsTests.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStrategy.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStructureSet.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp - ) - - target_link_libraries(UnitTests OrthancStone) - - add_custom_command( - TARGET UnitTests - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - "${ORTHANC_STONE_ROOT}/UnitTestsSources/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" - "$/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" - ) - -endif() - -##################################################################### -## Generate the documentation if Doxygen is present -##################################################################### - -find_package(Doxygen) -if (DOXYGEN_FOUND) - configure_file( - ${ORTHANC_STONE_ROOT}/Resources/OrthancStone.doxygen - ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen - @ONLY) - - add_custom_target(doc - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen - COMMENT "Generating documentation with Doxygen" VERBATIM - ) -else() - message("Doxygen not found. The documentation will not be built.") -endif() diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/CMakeLists.txt.old --- a/Applications/Samples/CMakeLists.txt.old Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,248 +0,0 @@ -# Usage: see README file - -cmake_minimum_required(VERSION 2.8.3) - -# Automatically link Qt executables to qtmain target on Windows -# ("OLD" == do not link) -if(POLICY CMP0020) - cmake_policy(SET CMP0020 OLD) -endif() - -# Only interpret if() arguments as variables or keywords when unquoted. -# NEW = do NOT dereference *quoted* variables -if(POLICY CMP0054) - cmake_policy(SET CMP0054 NEW) -endif() - -project(OrthancStone) - -include(../../Resources/CMake/OrthancStoneParameters.cmake) - -#set(ENABLE_DCMTK ON) - -set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") -set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") -set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") - -# TODO: replace or compute STONE_SOURCES_DIR from CMAKE_CURRENT_LIST_FILE - -if (ENABLE_WASM) - ##################################################################### - ## Configuration of the Emscripten compiler for WebAssembly target - ##################################################################### - - set(WASM_FLAGS "-s WASM=1 -O0 -g0") - message("*****************************************************************************") - message("WARNING: optimizations are disabled in emcc!!! Enable them for production use") - message("*****************************************************************************") - set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") - - # Handling of memory - #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") # Resize - #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") # 512MB - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912 -s TOTAL_STACK=128000000") # 512MB + resize - #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824") # 1GB + resize - - # To debug exceptions - #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2") - - add_definitions(-DORTHANC_ENABLE_WASM=1) - set(ORTHANC_SANDBOXED ON) - -elseif (ENABLE_QT OR ENABLE_SDL) - - set(ENABLE_NATIVE ON) - set(ORTHANC_SANDBOXED OFF) - set(ENABLE_CRYPTO_OPTIONS ON) - set(ENABLE_GOOGLE_TEST ON) - set(ENABLE_WEB_CLIENT ON) - -endif() - -##################################################################### -## Configuration for Orthanc -##################################################################### - -# include(../../Resources/CMake/Version.cmake) - -if (ORTHANC_STONE_VERSION STREQUAL "mainline") - set(ORTHANC_FRAMEWORK_VERSION "mainline") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") -else() - set(ORTHANC_FRAMEWORK_VERSION "1.4.1") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") -endif() - -set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") -set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") -set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") - -##################################################################### -## Build a static library containing the Orthanc Stone framework -##################################################################### - -LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) - -include(../../Resources/CMake/OrthancStoneConfiguration.cmake) - -add_library(OrthancStone STATIC - ${ORTHANC_STONE_SOURCES} - ) - -##################################################################### -## Build all the sample applications -##################################################################### - -include_directories(${ORTHANC_STONE_ROOT}) - -# files common to all samples -list(APPEND SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h - ) - -if (ENABLE_QT) - list(APPEND SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleQtApplicationRunner.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.cpp - ) - - ORTHANC_QT_WRAP_UI(SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.ui - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.ui - ) - - ORTHANC_QT_WRAP_CPP(SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Qt/QCairoWidget.h - ${ORTHANC_STONE_ROOT}/Applications/Qt/QStoneMainWindow.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.h - ) -endif() - -if (ENABLE_NATIVE) - list(APPEND SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainNative.cpp - ) - -elseif (ENABLE_WASM) - - list(APPEND SAMPLE_APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainWasm.cpp - ${STONE_WASM_SOURCES} - ) -endif() - - -macro(BuildSingleFileSample Target Header Sample) - add_executable(${Target} - ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header} - ${SAMPLE_APPLICATIONS_SOURCES} - ) - set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) - target_link_libraries(${Target} OrthancStone) - - if (ENABLE_QT AND (CMAKE_SYSTEM_NAME STREQUAL "Windows")) - message("(ENABLE_QT and (CMAKE_SYSTEM_NAME matches \"Windows\")) is true") - add_custom_command( - TARGET ${Target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - ) - endif() -endmacro() - -#BuildSingleFileSample(OrthancStoneEmpty EmptyApplication.h 1) -#BuildSingleFileSample(OrthancStoneTestPattern TestPatternApplication.h 2) -BuildSingleFileSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) -#BuildSingleFileSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) -#BuildSingleFileSample(OrthancStoneBasicPetCtFusion 5) -#BuildSingleFileSample(OrthancStoneSynchronizedSeries 6) -#BuildSingleFileSample(OrthancStoneLayoutPetCtFusion 7) -BuildSingleFileSample(OrthancStoneSimpleViewerSingleFile SimpleViewerApplicationSingleFile.h 8) # we keep that one just as a sample before we convert another sample to this pattern -BuildSingleFileSample(OrthancStoneSingleFrameEditor SingleFrameEditorApplication.h 9) - -##### SimpleViewer sample (Qt and WASM only) ####### - -if (ENABLE_QT OR ENABLE_WASM) - - # GenerateCodeFromFlatBufferSchema("${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ApplicationCommands.fbs") - - list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES ${FLATC_AUTOGENERATED_SOURCES}) - message(STATUS "SIMPLE_VIEWER_APPLICATION_SOURCES = ${SIMPLE_VIEWER_APPLICATION_SOURCES}") - message(STATUS "FLATC_AUTOGENERATED_SOURCES = ${FLATC_AUTOGENERATED_SOURCES}") - - if (ENABLE_QT) - list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/mainQt.cpp - ) - - ORTHANC_QT_WRAP_UI(SIMPLE_VIEWER_APPLICATION_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui - ) - - ORTHANC_QT_WRAP_CPP(SIMPLE_VIEWER_APPLICATION_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.h - ) - -elseif (ENABLE_WASM) - list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp - ${STONE_WASM_SOURCES} - ) - endif() - - add_executable(OrthancStoneSimpleViewer - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Messages.h - ${SIMPLE_VIEWER_APPLICATION_SOURCES} - ) - target_link_libraries(OrthancStoneSimpleViewer OrthancStone) - -endif() - -##################################################################### -## Build the unit tests -##################################################################### - -if (ENABLE_NATIVE) - add_executable(UnitTests - ${GOOGLE_TEST_SOURCES} - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestExceptions.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp - ) - - target_link_libraries(UnitTests OrthancStone) -endif() - -##################################################################### -## Generate the documentation if Doxygen is present -##################################################################### - -find_package(Doxygen) -if (DOXYGEN_FOUND) - configure_file( - ${ORTHANC_STONE_ROOT}/Resources/OrthancStone.doxygen - ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen - @ONLY) - - add_custom_target(doc - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen - COMMENT "Generating documentation with Doxygen" VERBATIM - ) -else() - message("Doxygen not found. The documentation will not be built.") -endif() diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/BasicPetCtFusionApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/BasicPetCtFusionApplication.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,202 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleInteractor.h" + +#include + +namespace OrthancStone +{ + namespace Samples + { + class BasicPetCtFusionApplication : public SampleApplicationBase + { + private: + class Interactor : public SampleInteractor + { + public: + static void SetStyle(LayeredSceneWidget& widget, + bool ct, + bool pet) + { + if (ct) + { + RenderStyle style; + style.windowing_ = ImageWindowing_Bone; + widget.SetLayerStyle(0, style); + } + else + { + RenderStyle style; + style.visible_ = false; + widget.SetLayerStyle(0, style); + } + + if (ct && pet) + { + RenderStyle style; + style.applyLut_ = true; + style.alpha_ = 0.5; + widget.SetLayerStyle(1, style); + } + else if (pet) + { + RenderStyle style; + style.applyLut_ = true; + widget.SetLayerStyle(1, style); + } + else + { + RenderStyle style; + style.visible_ = false; + widget.SetLayerStyle(1, style); + } + } + + + static bool IsVisible(LayeredSceneWidget& widget, + size_t layer) + { + RenderStyle style = widget.GetLayerStyle(layer); + return style.visible_; + } + + + static void ToggleInterpolation(LayeredSceneWidget& widget, + size_t layer) + { + RenderStyle style = widget.GetLayerStyle(layer); + + if (style.interpolation_ == ImageInterpolation_Bilinear) + { + style.interpolation_ = ImageInterpolation_Nearest; + } + else + { + style.interpolation_ = ImageInterpolation_Bilinear; + } + + widget.SetLayerStyle(layer, style); + } + + + Interactor(VolumeImage& volume, + VolumeProjection projection, + bool reverse) : + SampleInteractor(volume, projection, reverse) + { + } + + + virtual void KeyPressed(WorldSceneWidget& widget, + char key, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + LayeredSceneWidget& layered = dynamic_cast(widget); + + switch (key) + { + case 'c': + // Toggle the visibility of the CT layer + SetStyle(layered, !IsVisible(layered, 0), IsVisible(layered, 1)); + break; + + case 'p': + // Toggle the visibility of the PET layer + SetStyle(layered, IsVisible(layered, 0), !IsVisible(layered, 1)); + break; + + case 'i': + { + // Toggle on/off the interpolation + ToggleInterpolation(layered, 0); + ToggleInterpolation(layered, 1); + break; + } + + default: + break; + } + } + }; + + + public: + virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("ct", boost::program_options::value(), + "Orthanc ID of the CT series") + ("pet", boost::program_options::value(), + "Orthanc ID of the PET series") + ("threads", boost::program_options::value()->default_value(3), + "Number of download threads for the CT series") + ; + + options.add(generic); + } + + virtual void Initialize(BasicApplicationContext& context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + if (parameters.count("ct") != 1 || + parameters.count("pet") != 1) + { + LOG(ERROR) << "The series ID is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::string ct = parameters["ct"].as(); + std::string pet = parameters["pet"].as(); + unsigned int threads = parameters["threads"].as(); + + VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads); + VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1); + + // Take the PET volume as the reference for the slices + std::unique_ptr interactor(new Interactor(petVolume, VolumeProjection_Axial, false /* don't reverse normal */)); + + std::unique_ptr widget(new LayeredSceneWidget); + widget->AddLayer(new VolumeImage::LayerFactory(ctVolume)); + widget->AddLayer(new VolumeImage::LayerFactory(petVolume)); + widget->SetSlice(interactor->GetCursor().GetCurrentSlice()); + widget->SetInteractor(*interactor); + + Interactor::SetStyle(*widget, true, true); // Initially, show both CT and PET layers + + context.AddInteractor(interactor.release()); + context.SetCentralWidget(widget.release()); + + statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode"); + statusBar.SetMessage("Use the key \"c\" to show/hide the CT layer"); + statusBar.SetMessage("Use the key \"p\" to show/hide the PET layer"); + statusBar.SetMessage("Use the key \"i\" to toggle the smoothing of the images"); + } + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/CMakeLists.txt Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,292 @@ +# Usage (Linux): +# to build the WASM samples +# source ~/Downloads/emsdk/emsdk_env.sh && cmake -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc -DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON +# to build the Qt samples + +cmake_minimum_required(VERSION 2.8.3) +project(OrthancStone) + +include(../../../Resources/CMake/OrthancStoneParameters.cmake) + +set(ENABLE_STONE_DEPRECATED ON) # Need deprecated classes for these samples +set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON) + +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +set(ORTHANC_STONE_APPLICATION_RESOURCES + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +if (OPENSSL_NO_CAPIENG) +add_definitions(-DOPENSSL_NO_CAPIENG=1) +endif() + + +# the following block has been borrowed from orthanc/**/Compiler.cmake +if (MSVC_MULTIPLE_PROCESSES) +# "If you omit the processMax argument in the /MP option, the +# compiler obtains the number of effective processors from the +# operating system, and then creates one process per effective +# processor" +# https://blog.kitware.com/cmake-building-with-all-your-cores/ +# https://docs.microsoft.com/en-us/cpp/build/reference/mp-build-with-multiple-processes +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") +endif() + +#set(ENABLE_DCMTK ON) + +set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") +set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") +set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") + +if (ENABLE_WASM) + ##################################################################### + ## Configuration of the Emscripten compiler for WebAssembly target + ##################################################################### + + set(WASM_FLAGS "-s WASM=1") + set(WASM_FLAGS "${WASM_FLAGS} -s STRICT=1") # drops support for all deprecated build options + set(WASM_FLAGS "${WASM_FLAGS} -s FILESYSTEM=1") # if we don't include it, gen_uuid.c fails to build because srand, getpid(), ... are not defined + set(WASM_FLAGS "${WASM_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0") # actually enable exception catching + set(WASM_FLAGS "${WASM_FLAGS} -s ERROR_ON_MISSING_LIBRARIES=1") + + if (CMAKE_BUILD_TYPE MATCHES DEBUG) + set(WASM_FLAGS "${WASM_FLAGS} -g4") # generate debug information + set(WASM_FLAGS "${WASM_FLAGS} -s ASSERTIONS=2") # more runtime checks + else() + set(WASM_FLAGS "${WASM_FLAGS} -Os") # optimize for web (speed and size) + endif() + + set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module") + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${WASM_FLAGS}") # not always clear which flags are for the compiler and which one are for the linker -> pass them all to the linker too + # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"'") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_STACK=128000000") + + add_definitions(-DORTHANC_ENABLE_WASM=1) + set(ORTHANC_SANDBOXED ON) + +elseif (ENABLE_QT OR ENABLE_SDL) + + set(ENABLE_NATIVE ON) + set(ORTHANC_SANDBOXED OFF) + set(ENABLE_CRYPTO_OPTIONS ON) + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_WEB_CLIENT ON) + +else() + set(ENABLE_NATIVE ON) + set(ENABLE_OPENGL OFF) + +endif() + + +##################################################################### +## Configuration for Orthanc +##################################################################### + +# include(../../Resources/CMake/Version.cmake) + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.4.1") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + + +##################################################################### +## Build a static library containing the Orthanc Stone framework +##################################################################### + + +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(../../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +##################################################################### +## Build all the sample applications +##################################################################### + +include_directories(${ORTHANC_STONE_ROOT}) + +# files common to all samples +list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SampleInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SampleApplicationBase.h + ) + +if (ENABLE_QT) + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/Qt/SampleQtApplicationRunner.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/Qt/SampleMainWindow.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/Qt/SampleMainWindowWithButtons.cpp + ) + + ORTHANC_QT_WRAP_UI(SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/Qt/SampleMainWindow.ui + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/Qt/SampleMainWindowWithButtons.ui + ) + + ORTHANC_QT_WRAP_CPP(SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Qt/QCairoWidget.h + ${ORTHANC_STONE_ROOT}/Applications/Qt/QStoneMainWindow.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/Qt/SampleMainWindow.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/Qt/SampleMainWindowWithButtons.h + ) +endif() + +if (ENABLE_NATIVE) + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SampleMainNative.cpp + ) + +elseif (ENABLE_WASM) + + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SampleMainWasm.cpp + ${STONE_WASM_SOURCES} + ) +endif() + + +macro(BuildSingleFileSample Target Header Sample) + add_executable(${Target} + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/${Header} + ${SAMPLE_APPLICATIONS_SOURCES} + ) + set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) + target_link_libraries(${Target} OrthancStone) +endmacro() + + +if (ENABLE_SDL) + #BuildSingleFileSample(OrthancStoneEmpty EmptyApplication.h 1) + #BuildSingleFileSample(OrthancStoneTestPattern TestPatternApplication.h 2) + BuildSingleFileSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) + #BuildSingleFileSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) + #BuildSingleFileSample(OrthancStoneBasicPetCtFusion 5) + #BuildSingleFileSample(OrthancStoneSynchronizedSeries 6) + #BuildSingleFileSample(OrthancStoneLayoutPetCtFusion 7) + BuildSingleFileSample(OrthancStoneSimpleViewerSingleFile SimpleViewerApplicationSingleFile.h 8) # we keep that one just as a sample before we convert another sample to this pattern + BuildSingleFileSample(OrthancStoneSingleFrameEditor SingleFrameEditorApplication.h 9) +endif() + +##### SimpleViewer sample (Qt and WASM only) ####### + +if (ENABLE_QT OR ENABLE_WASM) + + if (ENABLE_QT) + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/Qt/SimpleViewerMainWindow.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/Qt/SimpleViewerMainWindow.ui + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/Qt/mainQt.cpp + ) + + ORTHANC_QT_WRAP_UI(SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/Qt/SimpleViewerMainWindow.ui + ) + + ORTHANC_QT_WRAP_CPP(SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/Qt/SimpleViewerMainWindow.h + ) + +elseif (ENABLE_WASM) + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/Wasm/mainWasm.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.h + ${STONE_WASM_SOURCES} + ) + endif() + + add_executable(OrthancStoneSimpleViewer + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/AppStatus.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/MainWidgetInteractor.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/MainWidgetInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/SimpleViewerApplication.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/SimpleViewerApplication.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/ThumbnailInteractor.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Deprecated/SimpleViewer/ThumbnailInteractor.h + ${SIMPLE_VIEWER_APPLICATION_SOURCES} + ) + target_link_libraries(OrthancStoneSimpleViewer OrthancStone) + + BuildSingleFileSample(OrthancStoneSingleFrameEditor SingleFrameEditorApplication.h 9) +endif() + +##################################################################### +## Build the unit tests +##################################################################### + +if (ENABLE_NATIVE) + add_executable(UnitTests + ${GOOGLE_TEST_SOURCES} + ${ORTHANC_STONE_ROOT}/UnitTestsSources/GenericToolboxTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/ImageToolboxTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/PixelTestPatternsTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStrategy.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStructureSet.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp + ) + + target_link_libraries(UnitTests OrthancStone) + + add_custom_command( + TARGET UnitTests + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${ORTHANC_STONE_ROOT}/UnitTestsSources/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" + "$/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" + ) + +endif() + +##################################################################### +## Generate the documentation if Doxygen is present +##################################################################### + +find_package(Doxygen) +if (DOXYGEN_FOUND) + configure_file( + ${ORTHANC_STONE_ROOT}/Resources/OrthancStone.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen + @ONLY) + + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen + COMMENT "Generating documentation with Doxygen" VERBATIM + ) +else() + message("Doxygen not found. The documentation will not be built.") +endif() diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/CMakeLists.txt.old --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/CMakeLists.txt.old Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,248 @@ +# Usage: see README file + +cmake_minimum_required(VERSION 2.8.3) + +# Automatically link Qt executables to qtmain target on Windows +# ("OLD" == do not link) +if(POLICY CMP0020) + cmake_policy(SET CMP0020 OLD) +endif() + +# Only interpret if() arguments as variables or keywords when unquoted. +# NEW = do NOT dereference *quoted* variables +if(POLICY CMP0054) + cmake_policy(SET CMP0054 NEW) +endif() + +project(OrthancStone) + +include(../../Resources/CMake/OrthancStoneParameters.cmake) + +#set(ENABLE_DCMTK ON) + +set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") +set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") +set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") + +# TODO: replace or compute STONE_SOURCES_DIR from CMAKE_CURRENT_LIST_FILE + +if (ENABLE_WASM) + ##################################################################### + ## Configuration of the Emscripten compiler for WebAssembly target + ##################################################################### + + set(WASM_FLAGS "-s WASM=1 -O0 -g0") + message("*****************************************************************************") + message("WARNING: optimizations are disabled in emcc!!! Enable them for production use") + message("*****************************************************************************") + set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") + + # Handling of memory + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") # Resize + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") # 512MB + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=536870912 -s TOTAL_STACK=128000000") # 512MB + resize + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=1073741824") # 1GB + resize + + # To debug exceptions + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=2") + + add_definitions(-DORTHANC_ENABLE_WASM=1) + set(ORTHANC_SANDBOXED ON) + +elseif (ENABLE_QT OR ENABLE_SDL) + + set(ENABLE_NATIVE ON) + set(ORTHANC_SANDBOXED OFF) + set(ENABLE_CRYPTO_OPTIONS ON) + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_WEB_CLIENT ON) + +endif() + +##################################################################### +## Configuration for Orthanc +##################################################################### + +# include(../../Resources/CMake/Version.cmake) + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.4.1") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + +##################################################################### +## Build a static library containing the Orthanc Stone framework +##################################################################### + +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +##################################################################### +## Build all the sample applications +##################################################################### + +include_directories(${ORTHANC_STONE_ROOT}) + +# files common to all samples +list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h + ) + +if (ENABLE_QT) + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleQtApplicationRunner.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.cpp + ) + + ORTHANC_QT_WRAP_UI(SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.ui + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.ui + ) + + ORTHANC_QT_WRAP_CPP(SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Qt/QCairoWidget.h + ${ORTHANC_STONE_ROOT}/Applications/Qt/QStoneMainWindow.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindow.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/Qt/SampleMainWindowWithButtons.h + ) +endif() + +if (ENABLE_NATIVE) + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainNative.cpp + ) + +elseif (ENABLE_WASM) + + list(APPEND SAMPLE_APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleMainWasm.cpp + ${STONE_WASM_SOURCES} + ) +endif() + + +macro(BuildSingleFileSample Target Header Sample) + add_executable(${Target} + ${ORTHANC_STONE_ROOT}/Applications/Samples/${Header} + ${SAMPLE_APPLICATIONS_SOURCES} + ) + set_target_properties(${Target} PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=${Sample}) + target_link_libraries(${Target} OrthancStone) + + if (ENABLE_QT AND (CMAKE_SYSTEM_NAME STREQUAL "Windows")) + message("(ENABLE_QT and (CMAKE_SYSTEM_NAME matches \"Windows\")) is true") + add_custom_command( + TARGET ${Target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + ) + endif() +endmacro() + +#BuildSingleFileSample(OrthancStoneEmpty EmptyApplication.h 1) +#BuildSingleFileSample(OrthancStoneTestPattern TestPatternApplication.h 2) +BuildSingleFileSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) +#BuildSingleFileSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) +#BuildSingleFileSample(OrthancStoneBasicPetCtFusion 5) +#BuildSingleFileSample(OrthancStoneSynchronizedSeries 6) +#BuildSingleFileSample(OrthancStoneLayoutPetCtFusion 7) +BuildSingleFileSample(OrthancStoneSimpleViewerSingleFile SimpleViewerApplicationSingleFile.h 8) # we keep that one just as a sample before we convert another sample to this pattern +BuildSingleFileSample(OrthancStoneSingleFrameEditor SingleFrameEditorApplication.h 9) + +##### SimpleViewer sample (Qt and WASM only) ####### + +if (ENABLE_QT OR ENABLE_WASM) + + # GenerateCodeFromFlatBufferSchema("${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ApplicationCommands.fbs") + + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES ${FLATC_AUTOGENERATED_SOURCES}) + message(STATUS "SIMPLE_VIEWER_APPLICATION_SOURCES = ${SIMPLE_VIEWER_APPLICATION_SOURCES}") + message(STATUS "FLATC_AUTOGENERATED_SOURCES = ${FLATC_AUTOGENERATED_SOURCES}") + + if (ENABLE_QT) + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/mainQt.cpp + ) + + ORTHANC_QT_WRAP_UI(SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui + ) + + ORTHANC_QT_WRAP_CPP(SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.h + ) + +elseif (ENABLE_WASM) + list(APPEND SIMPLE_VIEWER_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp + ${STONE_WASM_SOURCES} + ) + endif() + + add_executable(OrthancStoneSimpleViewer + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/AppStatus.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SimpleViewer/Messages.h + ${SIMPLE_VIEWER_APPLICATION_SOURCES} + ) + target_link_libraries(OrthancStoneSimpleViewer OrthancStone) + +endif() + +##################################################################### +## Build the unit tests +##################################################################### + +if (ENABLE_NATIVE) + add_executable(UnitTests + ${GOOGLE_TEST_SOURCES} + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestExceptions.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp + ) + + target_link_libraries(UnitTests OrthancStone) +endif() + +##################################################################### +## Generate the documentation if Doxygen is present +##################################################################### + +find_package(Doxygen) +if (DOXYGEN_FOUND) + configure_file( + ${ORTHANC_STONE_ROOT}/Resources/OrthancStone.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen + @ONLY) + + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen + COMMENT "Generating documentation with Doxygen" VERBATIM + ) +else() + message("Doxygen not found. The documentation will not be built.") +endif() diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/EmptyApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/EmptyApplication.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleApplicationBase.h" + +#include "../../../Framework/Widgets/EmptyWidget.h" + +namespace OrthancStone +{ + namespace Samples + { + class EmptyApplication : public SampleApplicationBase + { + public: + virtual void DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("red", boost::program_options::value()->default_value(255), "Background color: red channel") + ("green", boost::program_options::value()->default_value(0), "Background color: green channel") + ("blue", boost::program_options::value()->default_value(0), "Background color: blue channel") + ; + + options.add(generic); + } + + virtual void Initialize(IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + int red = parameters["red"].as(); + int green = parameters["green"].as(); + int blue = parameters["blue"].as(); + + context_->SetCentralWidget(new EmptyWidget(red, green, blue)); + } + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/LayoutPetCtFusionApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/LayoutPetCtFusionApplication.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,398 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleInteractor.h" + +#include "../../../Framework/Layers/ReferenceLineFactory.h" +#include "../../../Framework/Layers/DicomStructureSetSlicer.h" +#include "../../../Framework/Widgets/LayoutWidget.h" + +#include + +namespace OrthancStone +{ + namespace Samples + { + class LayoutPetCtFusionApplication : + public SampleApplicationBase, + public LayeredSceneWidget::ISliceObserver, + public WorldSceneWidget::IWorldObserver + { + private: + class Interactor : public SampleInteractor + { + private: + LayoutPetCtFusionApplication& that_; + + public: + Interactor(LayoutPetCtFusionApplication& that, + VolumeImage& volume, + VolumeProjection projection, + bool reverse) : + SampleInteractor(volume, projection, reverse), + that_(that) + { + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const SliceGeometry& slice, + const ViewportGeometry& view, + MouseButton button, + double x, + double y, + IStatusBar* statusBar) + { + if (button == MouseButton_Left) + { + // Center the sibling views over the clicked point + Vector p = slice.MapSliceToWorldCoordinates(x, y); + + if (statusBar != NULL) + { + char buf[64]; + sprintf(buf, "Click on coordinates (%.02f,%.02f,%.02f) in cm", p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); + statusBar->SetMessage(buf); + } + + that_.interactorAxial_->LookupSliceContainingPoint(*that_.ctAxial_, p); + that_.interactorCoronal_->LookupSliceContainingPoint(*that_.ctCoronal_, p); + that_.interactorSagittal_->LookupSliceContainingPoint(*that_.ctSagittal_, p); + } + + return NULL; + } + + virtual void KeyPressed(WorldSceneWidget& widget, + char key, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + if (key == 's') + { + that_.FitContent(); + } + } + }; + + bool processingEvent_; + Interactor* interactorAxial_; + Interactor* interactorCoronal_; + Interactor* interactorSagittal_; + LayeredSceneWidget* ctAxial_; + LayeredSceneWidget* ctCoronal_; + LayeredSceneWidget* ctSagittal_; + LayeredSceneWidget* petAxial_; + LayeredSceneWidget* petCoronal_; + LayeredSceneWidget* petSagittal_; + LayeredSceneWidget* fusionAxial_; + LayeredSceneWidget* fusionCoronal_; + LayeredSceneWidget* fusionSagittal_; + + + void FitContent() + { + petAxial_->FitContent(); + petCoronal_->FitContent(); + petSagittal_->FitContent(); + } + + + void AddLayer(LayeredSceneWidget& widget, + VolumeImage& volume, + bool isCt) + { + size_t layer; + widget.AddLayer(layer, new VolumeImage::LayerFactory(volume)); + + if (isCt) + { + RenderStyle style; + style.windowing_ = ImageWindowing_Bone; + widget.SetLayerStyle(layer, style); + } + else + { + RenderStyle style; + style.applyLut_ = true; + style.alpha_ = (layer == 0 ? 1.0f : 0.5f); + widget.SetLayerStyle(layer, style); + } + } + + + void ConnectSiblingLocations(LayeredSceneWidget& axial, + LayeredSceneWidget& coronal, + LayeredSceneWidget& sagittal) + { + ReferenceLineFactory::Configure(axial, coronal); + ReferenceLineFactory::Configure(axial, sagittal); + ReferenceLineFactory::Configure(coronal, sagittal); + } + + + void SynchronizeView(const WorldSceneWidget& source, + const ViewportGeometry& view, + LayeredSceneWidget& widget1, + LayeredSceneWidget& widget2, + LayeredSceneWidget& widget3) + { + if (&source == &widget1 || + &source == &widget2 || + &source == &widget3) + { + if (&source != &widget1) + { + widget1.SetView(view); + } + + if (&source != &widget2) + { + widget2.SetView(view); + } + + if (&source != &widget3) + { + widget3.SetView(view); + } + } + } + + + void SynchronizeSlice(const LayeredSceneWidget& source, + const SliceGeometry& slice, + LayeredSceneWidget& widget1, + LayeredSceneWidget& widget2, + LayeredSceneWidget& widget3) + { + if (&source == &widget1 || + &source == &widget2 || + &source == &widget3) + { + if (&source != &widget1) + { + widget1.SetSlice(slice); + } + + if (&source != &widget2) + { + widget2.SetSlice(slice); + } + + if (&source != &widget3) + { + widget3.SetSlice(slice); + } + } + } + + + LayeredSceneWidget* CreateWidget() + { + std::unique_ptr widget(new LayeredSceneWidget); + widget->Register(dynamic_cast(*this)); + widget->Register(dynamic_cast(*this)); + return widget.release(); + } + + + void CreateLayout(BasicApplicationContext& context) + { + std::unique_ptr layout(new OrthancStone::LayoutWidget); + layout->SetBackgroundCleared(true); + //layout->SetBackgroundColor(255,0,0); + layout->SetPadding(5); + + OrthancStone::LayoutWidget& layoutA = dynamic_cast + (layout->AddWidget(new OrthancStone::LayoutWidget)); + layoutA.SetPadding(0, 0, 0, 0, 5); + layoutA.SetVertical(); + petAxial_ = &dynamic_cast(layoutA.AddWidget(CreateWidget())); + OrthancStone::LayoutWidget& layoutA2 = dynamic_cast + (layoutA.AddWidget(new OrthancStone::LayoutWidget)); + layoutA2.SetPadding(0, 0, 0, 0, 5); + petSagittal_ = &dynamic_cast(layoutA2.AddWidget(CreateWidget())); + petCoronal_ = &dynamic_cast(layoutA2.AddWidget(CreateWidget())); + + OrthancStone::LayoutWidget& layoutB = dynamic_cast + (layout->AddWidget(new OrthancStone::LayoutWidget)); + layoutB.SetPadding(0, 0, 0, 0, 5); + layoutB.SetVertical(); + ctAxial_ = &dynamic_cast(layoutB.AddWidget(CreateWidget())); + OrthancStone::LayoutWidget& layoutB2 = dynamic_cast + (layoutB.AddWidget(new OrthancStone::LayoutWidget)); + layoutB2.SetPadding(0, 0, 0, 0, 5); + ctSagittal_ = &dynamic_cast(layoutB2.AddWidget(CreateWidget())); + ctCoronal_ = &dynamic_cast(layoutB2.AddWidget(CreateWidget())); + + OrthancStone::LayoutWidget& layoutC = dynamic_cast + (layout->AddWidget(new OrthancStone::LayoutWidget)); + layoutC.SetPadding(0, 0, 0, 0, 5); + layoutC.SetVertical(); + fusionAxial_ = &dynamic_cast(layoutC.AddWidget(CreateWidget())); + OrthancStone::LayoutWidget& layoutC2 = dynamic_cast + (layoutC.AddWidget(new OrthancStone::LayoutWidget)); + layoutC2.SetPadding(0, 0, 0, 0, 5); + fusionSagittal_ = &dynamic_cast(layoutC2.AddWidget(CreateWidget())); + fusionCoronal_ = &dynamic_cast(layoutC2.AddWidget(CreateWidget())); + + context.SetCentralWidget(layout.release()); + } + + + public: + virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("ct", boost::program_options::value(), + "Orthanc ID of the CT series") + ("pet", boost::program_options::value(), + "Orthanc ID of the PET series") + ("rt", boost::program_options::value(), + "Orthanc ID of the DICOM RT-STRUCT series (optional)") + ("threads", boost::program_options::value()->default_value(3), + "Number of download threads for the CT series") + ; + + options.add(generic); + } + + virtual void Initialize(BasicApplicationContext& context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + processingEvent_ = true; + + if (parameters.count("ct") != 1 || + parameters.count("pet") != 1) + { + LOG(ERROR) << "The series ID is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::string ct = parameters["ct"].as(); + std::string pet = parameters["pet"].as(); + unsigned int threads = parameters["threads"].as(); + + VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads); + VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1); + + // Take the PET volume as the reference for the slices + interactorAxial_ = &dynamic_cast + (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Axial, false))); + interactorCoronal_ = &dynamic_cast + (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Coronal, false))); + interactorSagittal_ = &dynamic_cast + (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Sagittal, true))); + + CreateLayout(context); + + AddLayer(*ctAxial_, ctVolume, true); + AddLayer(*ctCoronal_, ctVolume, true); + AddLayer(*ctSagittal_, ctVolume, true); + + AddLayer(*petAxial_, petVolume, false); + AddLayer(*petCoronal_, petVolume, false); + AddLayer(*petSagittal_, petVolume, false); + + AddLayer(*fusionAxial_, ctVolume, true); + AddLayer(*fusionAxial_, petVolume, false); + AddLayer(*fusionCoronal_, ctVolume, true); + AddLayer(*fusionCoronal_, petVolume, false); + AddLayer(*fusionSagittal_, ctVolume, true); + AddLayer(*fusionSagittal_, petVolume, false); + + if (parameters.count("rt") == 1) + { + DicomStructureSet& rtStruct = context.AddStructureSet(parameters["rt"].as()); + + Vector p = rtStruct.GetStructureCenter(0); + interactorAxial_->GetCursor().LookupSliceContainingPoint(p); + + ctAxial_->AddLayer(new DicomStructureSetSlicer(rtStruct)); + petAxial_->AddLayer(new DicomStructureSetSlicer(rtStruct)); + fusionAxial_->AddLayer(new DicomStructureSetSlicer(rtStruct)); + } + + ConnectSiblingLocations(*ctAxial_, *ctCoronal_, *ctSagittal_); + ConnectSiblingLocations(*petAxial_, *petCoronal_, *petSagittal_); + ConnectSiblingLocations(*fusionAxial_, *fusionCoronal_, *fusionSagittal_); + + interactorAxial_->AddWidget(*ctAxial_); + interactorAxial_->AddWidget(*petAxial_); + interactorAxial_->AddWidget(*fusionAxial_); + + interactorCoronal_->AddWidget(*ctCoronal_); + interactorCoronal_->AddWidget(*petCoronal_); + interactorCoronal_->AddWidget(*fusionCoronal_); + + interactorSagittal_->AddWidget(*ctSagittal_); + interactorSagittal_->AddWidget(*petSagittal_); + interactorSagittal_->AddWidget(*fusionSagittal_); + + processingEvent_ = false; + + statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode"); + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); + } + + virtual void NotifySizeChange(const WorldSceneWidget& source, + ViewportGeometry& view) + { + view.FitContent(); + } + + virtual void NotifyViewChange(const WorldSceneWidget& source, + const ViewportGeometry& view) + { + if (!processingEvent_) // Avoid reentrant calls + { + processingEvent_ = true; + + SynchronizeView(source, view, *ctAxial_, *petAxial_, *fusionAxial_); + SynchronizeView(source, view, *ctCoronal_, *petCoronal_, *fusionCoronal_); + SynchronizeView(source, view, *ctSagittal_, *petSagittal_, *fusionSagittal_); + + processingEvent_ = false; + } + } + + virtual void NotifySliceContentChange(const LayeredSceneWidget& source, + const SliceGeometry& slice) + { + if (!processingEvent_) // Avoid reentrant calls + { + processingEvent_ = true; + + SynchronizeSlice(source, slice, *ctAxial_, *petAxial_, *fusionAxial_); + SynchronizeSlice(source, slice, *ctCoronal_, *petCoronal_, *fusionCoronal_); + SynchronizeSlice(source, slice, *ctSagittal_, *petSagittal_, *fusionSagittal_); + + processingEvent_ = false; + } + } + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Qt/SampleMainWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Qt/SampleMainWindow.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "SampleMainWindow.h" + +/** + * Don't use "ui_MainWindow.h" instead of below, as + * this makes CMake unable to detect when the UI file changes. + **/ +#include +#include "../../../Applications/Samples/SampleApplicationBase.h" + +namespace OrthancStone +{ + namespace Samples + { + + SampleMainWindow::SampleMainWindow( + OrthancStone::NativeStoneApplicationContext& context, + OrthancStone::Samples::SampleSingleCanvasApplicationBase& stoneSampleApplication, + QWidget *parent) : + QStoneMainWindow(context, parent), + ui_(new Ui::SampleMainWindow), + stoneSampleApplication_(stoneSampleApplication) + { + ui_->setupUi(this); + SetCentralStoneWidget(*ui_->cairoCentralWidget); + } + + SampleMainWindow::~SampleMainWindow() + { + delete ui_; + } + + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Qt/SampleMainWindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Qt/SampleMainWindow.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,50 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ +#pragma once + +#include "../../../Qt/QCairoWidget.h" +#include "../../../Qt/QStoneMainWindow.h" + +namespace Ui +{ + class SampleMainWindow; +} + +namespace OrthancStone +{ + namespace Samples + { + + class SampleSingleCanvasApplicationBase; + + class SampleMainWindow : public QStoneMainWindow + { + Q_OBJECT + + private: + Ui::SampleMainWindow* ui_; + SampleSingleCanvasApplicationBase& stoneSampleApplication_; + + public: + explicit SampleMainWindow(OrthancStone::NativeStoneApplicationContext& context, SampleSingleCanvasApplicationBase& stoneSampleApplication, QWidget *parent = 0); + ~SampleMainWindow(); + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Qt/SampleMainWindow.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Qt/SampleMainWindow.ui Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,84 @@ + + + SampleMainWindow + + + + 0 + 0 + 903 + 634 + + + + + 500 + 300 + + + + + 500 + 300 + + + + Stone of Orthanc + + + Qt::LeftToRight + + + + + 0 + 0 + + + + Qt::LeftToRight + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 500 + + + + + + + + + + 0 + 0 + 903 + 22 + + + + + Test + + + + + + + + + QCairoWidget + QGraphicsView +
QCairoWidget.h
+
+
+ + +
diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Qt/SampleMainWindowWithButtons.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Qt/SampleMainWindowWithButtons.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,96 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "SampleMainWindow.h" + +/** + * Don't use "ui_MainWindow.h" instead of below, as + * this makes CMake unable to detect when the UI file changes. + **/ +#include +#include "../../../Applications/Samples/SampleApplicationBase.h" + +namespace OrthancStone +{ + namespace Samples + { + + SampleMainWindowWithButtons::SampleMainWindowWithButtons( + OrthancStone::NativeStoneApplicationContext& context, + OrthancStone::Samples::SampleSingleCanvasWithButtonsApplicationBase& stoneSampleApplication, + QWidget *parent) : + QStoneMainWindow(context, parent), + ui_(new Ui::SampleMainWindowWithButtons), + stoneSampleApplication_(stoneSampleApplication) + { + ui_->setupUi(this); + SetCentralStoneWidget(*ui_->cairoCentralWidget); + +#if QT_VERSION >= 0x050000 + connect(ui_->toolButton1, &QToolButton::clicked, this, &SampleMainWindowWithButtons::tool1Clicked); + connect(ui_->toolButton2, &QToolButton::clicked, this, &SampleMainWindowWithButtons::tool2Clicked); + connect(ui_->pushButton1, &QPushButton::clicked, this, &SampleMainWindowWithButtons::pushButton1Clicked); + connect(ui_->pushButton1, &QPushButton::clicked, this, &SampleMainWindowWithButtons::pushButton2Clicked); +#else + connect(ui_->toolButton1, SIGNAL(clicked()), this, SLOT(tool1Clicked())); + connect(ui_->toolButton2, SIGNAL(clicked()), this, SLOT(tool2Clicked())); + connect(ui_->pushButton1, SIGNAL(clicked()), this, SLOT(pushButton1Clicked())); + connect(ui_->pushButton1, SIGNAL(clicked()), this, SLOT(pushButton2Clicked())); +#endif + + std::string pushButton1Name; + std::string pushButton2Name; + std::string tool1Name; + std::string tool2Name; + stoneSampleApplication_.GetButtonNames(pushButton1Name, pushButton2Name, tool1Name, tool2Name); + + ui_->toolButton1->setText(QString::fromStdString(tool1Name)); + ui_->toolButton2->setText(QString::fromStdString(tool2Name)); + ui_->pushButton1->setText(QString::fromStdString(pushButton1Name)); + ui_->pushButton2->setText(QString::fromStdString(pushButton2Name)); + } + + SampleMainWindowWithButtons::~SampleMainWindowWithButtons() + { + delete ui_; + } + + void SampleMainWindowWithButtons::tool1Clicked() + { + stoneSampleApplication_.OnTool1Clicked(); + } + + void SampleMainWindowWithButtons::tool2Clicked() + { + stoneSampleApplication_.OnTool2Clicked(); + } + + void SampleMainWindowWithButtons::pushButton1Clicked() + { + stoneSampleApplication_.OnPushButton1Clicked(); + } + + void SampleMainWindowWithButtons::pushButton2Clicked() + { + stoneSampleApplication_.OnPushButton2Clicked(); + } + + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Qt/SampleMainWindowWithButtons.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Qt/SampleMainWindowWithButtons.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ +#pragma once + +#include "../../../Qt/QCairoWidget.h" +#include "../../../Qt/QStoneMainWindow.h" + +namespace Ui +{ + class SampleMainWindowWithButtons; +} + +namespace OrthancStone +{ + namespace Samples + { + + class SampleSingleCanvasWithButtonsApplicationBase; + + class SampleMainWindowWithButtons : public QStoneMainWindow + { + Q_OBJECT + + private: + Ui::SampleMainWindowWithButtons* ui_; + SampleSingleCanvasWithButtonsApplicationBase& stoneSampleApplication_; + + public: + explicit SampleMainWindowWithButtons(OrthancStone::NativeStoneApplicationContext& context, SampleSingleCanvasWithButtonsApplicationBase& stoneSampleApplication, QWidget *parent = 0); + ~SampleMainWindowWithButtons(); + + private slots: + void tool1Clicked(); + void tool2Clicked(); + void pushButton1Clicked(); + void pushButton2Clicked(); + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Qt/SampleMainWindowWithButtons.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Qt/SampleMainWindowWithButtons.ui Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,130 @@ + + + SampleMainWindowWithButtons + + + + 0 + 0 + 903 + 634 + + + + + 500 + 300 + + + + + 500 + 300 + + + + Stone of Orthanc + + + Qt::LeftToRight + + + + + 0 + 0 + + + + Qt::LeftToRight + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 500 + + + + + + + + + 0 + 100 + + + + + 16777215 + 100 + + + + + + + tool1 + + + + + + + tool2 + + + + + + + action1 + + + + + + + action2 + + + + + + + + + + + + 0 + 0 + 903 + 22 + + + + + Test + + + + + + + + + QCairoWidget + QGraphicsView +
QCairoWidget.h
+
+
+ + +
diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Qt/SampleQtApplicationRunner.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Qt/SampleQtApplicationRunner.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,50 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../../Qt/QtStoneApplicationRunner.h" + +#if ORTHANC_ENABLE_QT != 1 +#error this file shall be included only with the ORTHANC_ENABLE_QT set to 1 +#endif + +namespace OrthancStone +{ + namespace Samples + { + class SampleQtApplicationRunner : public OrthancStone::QtStoneApplicationRunner + { + protected: + virtual void InitializeMainWindow(OrthancStone::NativeStoneApplicationContext& context) + { + window_.reset(application_.CreateQtMainWindow()); + } + public: + SampleQtApplicationRunner(MessageBroker& broker, + SampleApplicationBase& application) + : OrthancStone::QtStoneApplicationRunner(broker, application) + { + } + + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SampleApplicationBase.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SampleApplicationBase.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,133 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../../Applications/IStoneApplication.h" +#include "../../../Framework/Deprecated/Widgets/WorldSceneWidget.h" + +#if ORTHANC_ENABLE_WASM==1 +#include "../../../Platforms/Wasm/WasmPlatformApplicationAdapter.h" +#include "../../../Platforms/Wasm/Defaults.h" +#endif + +#if ORTHANC_ENABLE_QT==1 +#include "Qt/SampleMainWindow.h" +#include "Qt/SampleMainWindowWithButtons.h" +#endif + +namespace OrthancStone +{ + namespace Samples + { + class SampleApplicationBase : public IStoneApplication + { + private: + boost::shared_ptr mainWidget_; + + public: + virtual void Initialize(StoneApplicationContext* context, + Deprecated::IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE + { + } + + virtual std::string GetTitle() const ORTHANC_OVERRIDE + { + return "Stone of Orthanc - Sample"; + } + + /** + * In the basic samples, the commands are handled by the platform adapter and NOT + * by the application handler + */ + virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE {}; + + + virtual void Finalize() ORTHANC_OVERRIDE {} + + virtual void SetCentralWidget(boost::shared_ptr widget) ORTHANC_OVERRIDE + { + mainWidget_ = widget; + } + + virtual boost::shared_ptr GetCentralWidget() ORTHANC_OVERRIDE + { + return mainWidget_; + } + +#if ORTHANC_ENABLE_WASM==1 + // default implementations for a single canvas named "canvas" in the HTML and an emtpy WasmApplicationAdapter + + virtual void InitializeWasm() ORTHANC_OVERRIDE + { + AttachWidgetToWasmViewport("canvas", mainWidget_); + } + + virtual WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(MessageBroker& broker) + { + return new WasmPlatformApplicationAdapter(broker, *this); + } +#endif + + }; + + // this application actually works in Qt and WASM + class SampleSingleCanvasWithButtonsApplicationBase : public SampleApplicationBase + { +public: + virtual void OnPushButton1Clicked() {} + virtual void OnPushButton2Clicked() {} + virtual void OnTool1Clicked() {} + virtual void OnTool2Clicked() {} + + virtual void GetButtonNames(std::string& pushButton1, + std::string& pushButton2, + std::string& tool1, + std::string& tool2 + ) { + pushButton1 = "action1"; + pushButton2 = "action2"; + tool1 = "tool1"; + tool2 = "tool2"; + } + +#if ORTHANC_ENABLE_QT==1 + virtual QStoneMainWindow* CreateQtMainWindow() { + return new SampleMainWindowWithButtons(dynamic_cast(*context_), *this); + } +#endif + + }; + + // this application actually works in SDL and WASM + class SampleSingleCanvasApplicationBase : public SampleApplicationBase + { +public: + +#if ORTHANC_ENABLE_QT==1 + virtual QStoneMainWindow* CreateQtMainWindow() { + return new SampleMainWindow(dynamic_cast(*context_), *this); + } +#endif + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SampleInteractor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SampleInteractor.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleApplicationBase.h" + +#include "../../../Framework/Widgets/LayeredSceneWidget.h" +#include "../../../Framework/Widgets/IWorldSceneInteractor.h" +#include "../../../Framework/Toolbox/ParallelSlicesCursor.h" + +namespace OrthancStone +{ + namespace Samples + { + /** + * This is a basic mouse interactor for sample applications. It + * contains a set of parallel slices in the 3D space. The mouse + * wheel events make the widget change the slice that is + * displayed. + **/ + class SampleInteractor : public IWorldSceneInteractor + { + private: + ParallelSlicesCursor cursor_; + + public: + SampleInteractor(VolumeImage& volume, + VolumeProjection projection, + bool reverse) + { + std::unique_ptr slices(volume.GetGeometry(projection, reverse)); + cursor_.SetGeometry(*slices); + } + + SampleInteractor(ISeriesLoader& series, + bool reverse) + { + if (reverse) + { + std::unique_ptr slices(series.GetGeometry().Reverse()); + cursor_.SetGeometry(*slices); + } + else + { + cursor_.SetGeometry(series.GetGeometry()); + } + } + + SampleInteractor(const ParallelSlices& slices) + { + cursor_.SetGeometry(slices); + } + + ParallelSlicesCursor& GetCursor() + { + return cursor_; + } + + void AddWidget(LayeredSceneWidget& widget) + { + widget.SetInteractor(*this); + widget.SetSlice(cursor_.GetCurrentSlice()); + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + MouseButton button, + double x, + double y, + IStatusBar* statusBar) + { + return NULL; + } + + virtual void MouseOver(CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) + { + } + + virtual void MouseWheel(WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + if (cursor_.ApplyWheelEvent(direction, modifiers)) + { + dynamic_cast(widget).SetSlice(cursor_.GetCurrentSlice()); + } + } + + virtual void KeyPressed(WorldSceneWidget& widget, + char key, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + } + + void LookupSliceContainingPoint(LayeredSceneWidget& widget, + const Vector& p) + { + if (cursor_.LookupSliceContainingPoint(p)) + { + widget.SetSlice(cursor_.GetCurrentSlice()); + } + } + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SampleList.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SampleList.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,41 @@ +// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script + +#if ORTHANC_STONE_SAMPLE == 1 +#include "EmptyApplication.h" +typedef OrthancStone::Samples::EmptyApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 2 +#include "TestPatternApplication.h" +typedef OrthancStone::Samples::TestPatternApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 3 +#include "SingleFrameApplication.h" +typedef OrthancStone::Samples::SingleFrameApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 4 +#include "SingleVolumeApplication.h" +typedef OrthancStone::Samples::SingleVolumeApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 5 +#include "BasicPetCtFusionApplication.h" +typedef OrthancStone::Samples::BasicPetCtFusionApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 6 +#include "SynchronizedSeriesApplication.h" +typedef OrthancStone::Samples::SynchronizedSeriesApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 7 +#include "LayoutPetCtFusionApplication.h" +typedef OrthancStone::Samples::LayoutPetCtFusionApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 8 +#include "SimpleViewerApplicationSingleFile.h" +typedef OrthancStone::Samples::SimpleViewerApplication SampleApplication; + +#elif ORTHANC_STONE_SAMPLE == 9 +#include "SingleFrameEditorApplication.h" +typedef OrthancStone::Samples::SingleFrameEditorApplication SampleApplication; + +#else +#error Please set the ORTHANC_STONE_SAMPLE macro +#endif diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SampleMainNative.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SampleMainNative.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,44 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "SampleList.h" +#if ORTHANC_ENABLE_SDL==1 +#include "../../Sdl/SdlStoneApplicationRunner.h" +#endif +#if ORTHANC_ENABLE_QT==1 +#include "Qt/SampleQtApplicationRunner.h" +#endif + +int main(int argc, char* argv[]) +{ + boost::shared_ptr sampleStoneApplication(new SampleApplication); + +#if ORTHANC_ENABLE_SDL==1 + OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(sampleStoneApplication); + return sdlApplicationRunner.Execute(argc, argv); +#endif + +#if ORTHANC_ENABLE_QT==1 + OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(sampleStoneApplication); + return qtAppRunner.Execute(argc, argv); +#endif +} + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SampleMainWasm.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SampleMainWasm.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,37 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "Platforms/Wasm/WasmWebService.h" +#include "Platforms/Wasm/WasmViewport.h" + +#include + +#include "SampleList.h" + + +OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) +{ + return new SampleApplication(broker); +} + +OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, OrthancStone::IStoneApplication* application) +{ + return dynamic_cast(application)->CreateWasmApplicationAdapter(broker); +} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Samples-status.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Samples-status.md Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,103 @@ +Executable versions +================ +Generic options +---------------------- +``` +("help", "Display this help and exit") +("verbose", "Be verbose in logs") +("orthanc", boost::program_options::value() + ->default_value("http://localhost:8042/"), + "URL to the Orthanc server") +("username", "Username for the Orthanc server") +("password", "Password for the Orthanc server") +("https-verify", boost::program_options::value() + ->default_value(true), "Check HTTPS certificates") +``` +OrthancStoneSimpleViewer +------------------------------------- +- Options: + ``` + - "studyId", std::string, "Orthanc ID of the study" + ``` +- study loading works OK +- Invert does not work: +``` +void SimpleViewerApplication::ExecuteAction(SimpleViewerApplication::Actions action) + { + // TODO + } +``` + +OrthancStoneSimpleViewerSingleFile +------------------------------------- +- Options: + ``` + - "studyId", std::string, "Orthanc ID of the study" + ``` + +Study loading works. + +The `line` and `circle` buttons work and call this: +``` +virtual void OnTool1Clicked() +{ + currentTool_ = Tools_LineMeasure; +} + +virtual void OnTool2Clicked() +{ + currentTool_ = Tools_CircleMeasure; +} +``` +The `action1` and `action2` buttons are not connected + +The following is displayed in the console at launch time: +``` +W0313 12:20:12.790449 NativeStoneApplicationRunner.cpp:55] Use the key "s" to reinitialize the layout +W0313 12:20:12.790449 NativeStoneApplicationRunner.cpp:55] Use the key "n" to go to next image in the main viewport +``` +However, when looking at `MainWidgetInteractor::KeyPressed` (`SimpleViewerApplicationSingleFile.h:169`), only the following is processed: +- 's': reset layout +- 'l': select line tool +- 'c': select circle tool + +OrthancStoneSingleFrame +------------------------------------- +``` +generic.add_options() +("instance", boost::program_options::value(), +"Orthanc ID of the instance") +("frame", boost::program_options::value() + ->default_value(0), +"Number of the frame, for multi-frame DICOM instances") +("smooth", boost::program_options::value() + ->default_value(true), +"Enable bilinear interpolation to smooth the image"); +``` +only key handled in `KeyPressed` is `s` to call `widget.FitContent()` + + +OrthancStoneSingleFrameEditor +------------------------------------- +``` +generic.add_options() +("instance", boost::program_options::value(), +"Orthanc ID of the instance") +("frame", boost::program_options::value() + ->default_value(0), +"Number of the frame, for multi-frame DICOM instances"); +``` +Available commands in `KeyPressed` (`SingleFrameEditorApplication.h:280`): +- 'a' widget.FitContent() +- 'c' Crop tool +- 'm' Mask tool +- 'd' dump to json and diplay result (?) +- 'e' export current view to Dicom with dummy tags (?) +- 'i' wdiget.SwitchInvert +- 't' Move tool +- 'n' switch between nearest and bilinear interpolation +- 'r' Rotate tool +- 's' Resize tool +- 'w' Windowing tool +- 'ctrl+y' redo +- 'ctrl+z' undo diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/AppStatus.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/AppStatus.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,27 @@ +#pragma once + +#include + + +namespace SimpleViewer +{ + struct AppStatus + { + std::string patientId; + std::string studyDescription; + std::string currentInstanceIdInMainViewport; + // note: if you add members here, update the serialization code below and deserialization in simple-viewer.ts -> onAppStatusUpdated() + + + AppStatus() + { + } + + void ToJson(Json::Value &output) const + { + output["patientId"] = patientId; + output["studyDescription"] = studyDescription; + output["currentInstanceIdInMainViewport"] = currentInstanceIdInMainViewport; + } + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/MainWidgetInteractor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/MainWidgetInteractor.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "MainWidgetInteractor.h" + +#include "SimpleViewerApplication.h" + +namespace SimpleViewer { + + Deprecated::IWorldSceneMouseTracker* MainWidgetInteractor::CreateMouseTracker(Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + Deprecated::IStatusBar* statusBar, + const std::vector& displayTouches) + { + if (button == MouseButton_Left) + { + if (application_.GetCurrentTool() == Tool_LineMeasure) + { + return new Deprecated::LineMeasureTracker(statusBar, dynamic_cast(widget).GetSlice(), + x, y, 255, 0, 0, application_.GetFont()); + } + else if (application_.GetCurrentTool() == Tool_CircleMeasure) + { + return new Deprecated::CircleMeasureTracker(statusBar, dynamic_cast(widget).GetSlice(), + x, y, 255, 0, 0, application_.GetFont()); + } + else if (application_.GetCurrentTool() == Tool_Crop) + { + // TODO + } + else if (application_.GetCurrentTool() == Tool_Windowing) + { + // TODO + } + else if (application_.GetCurrentTool() == Tool_Zoom) + { + // TODO + } + else if (application_.GetCurrentTool() == Tool_Pan) + { + // TODO + } + } + return NULL; + } + + void MainWidgetInteractor::MouseOver(CairoContext& context, + Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + double x, + double y, + Deprecated::IStatusBar* statusBar) + { + if (statusBar != NULL) + { + Vector p = dynamic_cast(widget).GetSlice().MapSliceToWorldCoordinates(x, y); + + char buf[64]; + sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", + p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); + statusBar->SetMessage(buf); + } + } + + void MainWidgetInteractor::MouseWheel(Deprecated::WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + { + } + + void MainWidgetInteractor::KeyPressed(Deprecated::WorldSceneWidget& widget, + KeyboardKeys key, + char keyChar, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + { + switch (keyChar) + { + case 's': + widget.FitContent(); + break; + + default: + break; + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/MainWidgetInteractor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/MainWidgetInteractor.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include "../../../../Framework/Deprecated/Widgets/IWorldSceneInteractor.h" + +using namespace OrthancStone; + +namespace SimpleViewer { + + class SimpleViewerApplication; + + class MainWidgetInteractor : public Deprecated::IWorldSceneInteractor + { + private: + SimpleViewerApplication& application_; + + public: + MainWidgetInteractor(SimpleViewerApplication& application) : + application_(application) + { + } + + /** + WorldSceneWidget: + */ + virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + Deprecated::IStatusBar* statusBar, + const std::vector& displayTouches); + + virtual void MouseOver(CairoContext& context, + Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + double x, + double y, + Deprecated::IStatusBar* statusBar); + + virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar); + + virtual void KeyPressed(Deprecated::WorldSceneWidget& widget, + KeyboardKeys key, + char keyChar, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar); + }; + + +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Qt/SimpleViewerMainWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Qt/SimpleViewerMainWindow.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,109 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "SimpleViewerMainWindow.h" + +/** + * Don't use "ui_MainWindow.h" instead of below, as + * this makes CMake unable to detect when the UI file changes. + **/ +#include +#include "../../SimpleViewerApplication.h" + + +namespace SimpleViewer +{ + template + bool ExecuteCommand(U* handler, const T& command) + { + std::string serializedCommand = StoneSerialize(command); + StoneDispatchToHandler(serializedCommand, handler); + } + + SimpleViewerMainWindow::SimpleViewerMainWindow( + OrthancStone::NativeStoneApplicationContext& context, + SimpleViewerApplication& stoneApplication, + QWidget *parent) : + QStoneMainWindow(context, parent), + ui_(new Ui::SimpleViewerMainWindow), + stoneApplication_(stoneApplication) + { + ui_->setupUi(this); + SetCentralStoneWidget(*ui_->cairoCentralWidget); + +#if QT_VERSION >= 0x050000 + connect(ui_->toolButtonCrop, &QToolButton::clicked, this, &SimpleViewerMainWindow::cropClicked); + connect(ui_->pushButtonUndoCrop, &QToolButton::clicked, this, &SimpleViewerMainWindow::undoCropClicked); + connect(ui_->toolButtonLine, &QToolButton::clicked, this, &SimpleViewerMainWindow::lineClicked); + connect(ui_->toolButtonCircle, &QToolButton::clicked, this, &SimpleViewerMainWindow::circleClicked); + connect(ui_->toolButtonWindowing, &QToolButton::clicked, this, &SimpleViewerMainWindow::windowingClicked); + connect(ui_->pushButtonRotate, &QPushButton::clicked, this, &SimpleViewerMainWindow::rotateClicked); + connect(ui_->pushButtonInvert, &QPushButton::clicked, this, &SimpleViewerMainWindow::invertClicked); +#else + connect(ui_->toolButtonCrop, SIGNAL(clicked()), this, SLOT(cropClicked())); + connect(ui_->toolButtonLine, SIGNAL(clicked()), this, SLOT(lineClicked())); + connect(ui_->toolButtonCircle, SIGNAL(clicked()), this, SLOT(circleClicked())); + connect(ui_->toolButtonWindowing, SIGNAL(clicked()), this, SLOT(windowingClicked())); + connect(ui_->pushButtonUndoCrop, SIGNAL(clicked()), this, SLOT(undoCropClicked())); + connect(ui_->pushButtonRotate, SIGNAL(clicked()), this, SLOT(rotateClicked())); + connect(ui_->pushButtonInvert, SIGNAL(clicked()), this, SLOT(invertClicked())); +#endif + } + + SimpleViewerMainWindow::~SimpleViewerMainWindow() + { + delete ui_; + } + + void SimpleViewerMainWindow::cropClicked() + { + stoneApplication_.ExecuteCommand(SelectTool(Tool_Crop)); + } + + void SimpleViewerMainWindow::undoCropClicked() + { + stoneApplication_.ExecuteCommand(Action(ActionType_UndoCrop)); + } + + void SimpleViewerMainWindow::lineClicked() + { + stoneApplication_.ExecuteCommand(SelectTool(Tool_LineMeasure)); + } + + void SimpleViewerMainWindow::circleClicked() + { + stoneApplication_.ExecuteCommand(SelectTool(Tool_CircleMeasure)); + } + + void SimpleViewerMainWindow::windowingClicked() + { + stoneApplication_.ExecuteCommand(SelectTool(Tool_Windowing)); + } + + void SimpleViewerMainWindow::rotateClicked() + { + stoneApplication_.ExecuteCommand(Action(ActionType_Rotate)); + } + + void SimpleViewerMainWindow::invertClicked() + { + stoneApplication_.ExecuteCommand(Action(ActionType_Invert)); + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Qt/SimpleViewerMainWindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Qt/SimpleViewerMainWindow.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,57 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ +#pragma once + +#include +#include + +namespace Ui +{ + class SimpleViewerMainWindow; +} + +using namespace OrthancStone; + +namespace SimpleViewer +{ + class SimpleViewerApplication; + + class SimpleViewerMainWindow : public QStoneMainWindow + { + Q_OBJECT + + private: + Ui::SimpleViewerMainWindow* ui_; + SimpleViewerApplication& stoneApplication_; + + public: + explicit SimpleViewerMainWindow(OrthancStone::NativeStoneApplicationContext& context, SimpleViewerApplication& stoneApplication, QWidget *parent = 0); + ~SimpleViewerMainWindow(); + + private slots: + void cropClicked(); + void undoCropClicked(); + void rotateClicked(); + void windowingClicked(); + void lineClicked(); + void circleClicked(); + void invertClicked(); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Qt/SimpleViewerMainWindow.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Qt/SimpleViewerMainWindow.ui Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,151 @@ + + + SimpleViewerMainWindow + + + + 0 + 0 + 903 + 634 + + + + + 500 + 300 + + + + + 500 + 300 + + + + Stone of Orthanc + + + Qt::LeftToRight + + + + + 0 + 0 + + + + Qt::LeftToRight + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 500 + + + + + + + + + 0 + 100 + + + + + 16777215 + 100 + + + + + + + windowing + + + + + + + crop + + + + + + + undo crop + + + + + + + line + + + + + + + circle + + + + + + + rotate + + + + + + + invert + + + + + + + + + + + + 0 + 0 + 903 + 22 + + + + + Test + + + + + + + + + QCairoWidget + QGraphicsView +
QCairoWidget.h
+
+
+ + +
diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Qt/mainQt.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Qt/mainQt.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,14 @@ +#include "Applications/Qt/QtStoneApplicationRunner.h" + +#include "../../SimpleViewerApplication.h" +#include "Framework/Messages/MessageBroker.h" + + +int main(int argc, char* argv[]) +{ + OrthancStone::MessageBroker broker; + SimpleViewer::SimpleViewerApplication stoneApplication(broker); + + OrthancStone::QtStoneApplicationRunner qtAppRunner(broker, stoneApplication); + return qtAppRunner.Execute(argc, argv); +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/SimpleViewerApplication.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/SimpleViewerApplication.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,225 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "SimpleViewerApplication.h" + +#if ORTHANC_ENABLE_QT == 1 +# include "Qt/SimpleViewerMainWindow.h" +#endif + +#if ORTHANC_ENABLE_WASM == 1 +# include +#endif + +namespace SimpleViewer +{ + + void SimpleViewerApplication::Initialize(StoneApplicationContext* context, + Deprecated::IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + context_ = context; + statusBar_ = &statusBar; + + {// initialize viewports and layout + mainLayout_ = new Deprecated::LayoutWidget("main-layout"); + mainLayout_->SetPadding(10); + mainLayout_->SetBackgroundCleared(true); + mainLayout_->SetBackgroundColor(0, 0, 0); + mainLayout_->SetHorizontal(); + + thumbnailsLayout_ = new Deprecated::LayoutWidget("thumbnail-layout"); + thumbnailsLayout_->SetPadding(10); + thumbnailsLayout_->SetBackgroundCleared(true); + thumbnailsLayout_->SetBackgroundColor(50, 50, 50); + thumbnailsLayout_->SetVertical(); + + mainWidget_ = new Deprecated::SliceViewerWidget(IObserver::GetBroker(), "main-viewport"); + //mainWidget_->RegisterObserver(*this); + + // hierarchy + mainLayout_->AddWidget(thumbnailsLayout_); + mainLayout_->AddWidget(mainWidget_); + + // sources + smartLoader_.reset(new Deprecated::SmartLoader(IObserver::GetBroker(), context->GetOrthancApiClient())); + smartLoader_->SetImageQuality(Deprecated::SliceImageQuality_FullPam); + + mainLayout_->SetTransmitMouseOver(true); + mainWidgetInteractor_.reset(new MainWidgetInteractor(*this)); + mainWidget_->SetInteractor(*mainWidgetInteractor_); + thumbnailInteractor_.reset(new ThumbnailInteractor(*this)); + } + + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); + statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport"); + + + if (parameters.count("studyId") < 1) + { + LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc"; + context->GetOrthancApiClient().GetJsonAsync("/studies", new Callable(*this, &SimpleViewerApplication::OnStudyListReceived)); + } + else + { + SelectStudy(parameters["studyId"].as()); + } + } + + + void SimpleViewerApplication::DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("studyId", boost::program_options::value(), + "Orthanc ID of the study") + ; + + options.add(generic); + } + + void SimpleViewerApplication::OnStudyListReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) + { + const Json::Value& response = message.GetJson(); + + if (response.isArray() && + response.size() >= 1) + { + SelectStudy(response[0].asString()); + } + } + void SimpleViewerApplication::OnStudyReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) + { + const Json::Value& response = message.GetJson(); + + if (response.isObject() && response["Series"].isArray()) + { + for (size_t i=0; i < response["Series"].size(); i++) + { + context_->GetOrthancApiClient().GetJsonAsync("/series/" + response["Series"][(int)i].asString(), new Callable(*this, &SimpleViewerApplication::OnSeriesReceived)); + } + } + } + + void SimpleViewerApplication::OnSeriesReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) + { + const Json::Value& response = message.GetJson(); + + if (response.isObject() && + response["Instances"].isArray() && + response["Instances"].size() > 0) + { + // keep track of all instances IDs + const std::string& seriesId = response["ID"].asString(); + seriesTags_[seriesId] = response; + instancesIdsPerSeriesId_[seriesId] = std::vector(); + for (size_t i = 0; i < response["Instances"].size(); i++) + { + const std::string& instanceId = response["Instances"][static_cast(i)].asString(); + instancesIdsPerSeriesId_[seriesId].push_back(instanceId); + } + + // load the first instance in the thumbnail + LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]); + + // if this is the first thumbnail loaded, load the first instance in the mainWidget + if (mainWidget_->GetLayerCount() == 0) + { + smartLoader_->SetFrameInWidget(*mainWidget_, 0, instancesIdsPerSeriesId_[seriesId][0], 0); + } + } + } + + void SimpleViewerApplication::LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId) + { + LOG(INFO) << "Loading thumbnail for series " << seriesId; + + Deprecated::SliceViewerWidget* thumbnailWidget = + new Deprecated::SliceViewerWidget(IObserver::GetBroker(), "thumbnail-series-" + seriesId); + thumbnails_.push_back(thumbnailWidget); + thumbnailsLayout_->AddWidget(thumbnailWidget); + + thumbnailWidget->RegisterObserverCallback( + new Callable + (*this, &SimpleViewerApplication::OnWidgetGeometryChanged)); + + smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0); + thumbnailWidget->SetInteractor(*thumbnailInteractor_); + } + + void SimpleViewerApplication::SelectStudy(const std::string& studyId) + { + context_->GetOrthancApiClient().GetJsonAsync("/studies/" + studyId, new Callable(*this, &SimpleViewerApplication::OnStudyReceived)); + } + + void SimpleViewerApplication::OnWidgetGeometryChanged(const Deprecated::SliceViewerWidget::GeometryChangedMessage& message) + { + // TODO: The "const_cast" could probably be replaced by "mainWidget_" + const_cast(message.GetOrigin()).FitContent(); + } + + void SimpleViewerApplication::SelectSeriesInMainViewport(const std::string& seriesId) + { + smartLoader_->SetFrameInWidget(*mainWidget_, 0, instancesIdsPerSeriesId_[seriesId][0], 0); + } + + bool SimpleViewerApplication::Handle(const StoneSampleCommands::SelectTool& value) + { + currentTool_ = value.tool; + return true; + } + + bool SimpleViewerApplication::Handle(const StoneSampleCommands::Action& value) + { + switch (value.type) + { + case ActionType_Invert: + // TODO + break; + case ActionType_UndoCrop: + // TODO + break; + case ActionType_Rotate: + // TODO + break; + default: + throw std::runtime_error("Action type not supported"); + } + return true; + } + +#if ORTHANC_ENABLE_QT==1 + QStoneMainWindow* SimpleViewerApplication::CreateQtMainWindow() + { + return new SimpleViewerMainWindow(dynamic_cast(*context_), *this); + } +#endif + +#if ORTHANC_ENABLE_WASM==1 + void SimpleViewerApplication::InitializeWasm() { + + AttachWidgetToWasmViewport("canvasThumbnails", thumbnailsLayout_); + AttachWidgetToWasmViewport("canvasMain", mainWidget_); + } +#endif + + +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/SimpleViewerApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/SimpleViewerApplication.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,175 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + + /* + This header contains the command definitions for the sample applications + */ +#include "Applications/Samples/StoneSampleCommands_generated.hpp" +using namespace StoneSampleCommands; + +#include "Applications/IStoneApplication.h" + +#include "../../../../Framework/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" +#include "Platforms/Wasm/Defaults.h" +#endif + +#if ORTHANC_ENABLE_QT==1 +#include "Qt/SimpleViewerMainWindow.h" +#endif + +#include +#include + +#include "ThumbnailInteractor.h" +#include "MainWidgetInteractor.h" +#include "AppStatus.h" + +using namespace OrthancStone; + + +namespace SimpleViewer +{ + + class SimpleViewerApplication + : public IStoneApplication + , public IObserver + , public IObservable + , public StoneSampleCommands::IHandler + { + public: + + struct StatusUpdatedMessage : public IMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + const AppStatus& status_; + + StatusUpdatedMessage(const AppStatus& status) + : status_(status) + { + } + }; + + private: + Tool currentTool_; + + std::unique_ptr mainWidgetInteractor_; + std::unique_ptr thumbnailInteractor_; + Deprecated::LayoutWidget* mainLayout_; + Deprecated::LayoutWidget* thumbnailsLayout_; + Deprecated::SliceViewerWidget* mainWidget_; + std::vector thumbnails_; + std::map > instancesIdsPerSeriesId_; + std::map seriesTags_; + unsigned int currentInstanceIndex_; + Deprecated::WidgetViewport* wasmViewport1_; + Deprecated::WidgetViewport* wasmViewport2_; + + Deprecated::IStatusBar* statusBar_; + std::unique_ptr smartLoader_; + + Orthanc::Font font_; + + public: + SimpleViewerApplication(MessageBroker& broker) : + IObserver(broker), + IObservable(broker), + currentTool_(StoneSampleCommands::Tool_LineMeasure), + mainLayout_(NULL), + currentInstanceIndex_(0), + wasmViewport1_(NULL), + wasmViewport2_(NULL) + { + font_.LoadFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); + } + + virtual void Finalize() ORTHANC_OVERRIDE {} + virtual Deprecated::IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainLayout_;} + + virtual void DeclareStartupOptions(boost::program_options::options_description& options) ORTHANC_OVERRIDE; + virtual void Initialize(StoneApplicationContext* context, + Deprecated::IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE; + + void OnStudyListReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message); + + void OnStudyReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message); + + void OnSeriesReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message); + + void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId); + + void SelectStudy(const std::string& studyId); + + void OnWidgetGeometryChanged(const Deprecated::SliceViewerWidget::GeometryChangedMessage& message); + + void SelectSeriesInMainViewport(const std::string& seriesId); + + + Tool GetCurrentTool() const + { + return currentTool_; + } + + const Orthanc::Font& GetFont() const + { + return font_; + } + + // ExecuteAction method was empty (its body was a single "TODO" comment) + virtual bool Handle(const SelectTool& value) ORTHANC_OVERRIDE; + virtual bool Handle(const Action& value) ORTHANC_OVERRIDE; + + template + bool ExecuteCommand(const T& cmd) + { + std::string cmdStr = StoneSampleCommands::StoneSerialize(cmd); + return StoneSampleCommands::StoneDispatchToHandler(cmdStr, this); + } + + virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE + { + StoneSampleCommands::StoneDispatchToHandler(data, this); + } + + virtual std::string GetTitle() const ORTHANC_OVERRIDE {return "SimpleViewer";} + +#if ORTHANC_ENABLE_WASM==1 + virtual void InitializeWasm() ORTHANC_OVERRIDE; +#endif + +#if ORTHANC_ENABLE_QT==1 + virtual QStoneMainWindow* CreateQtMainWindow(); +#endif + }; + + +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/ThumbnailInteractor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/ThumbnailInteractor.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,46 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "ThumbnailInteractor.h" + +#include "SimpleViewerApplication.h" + +namespace SimpleViewer { + + Deprecated::IWorldSceneMouseTracker* ThumbnailInteractor::CreateMouseTracker(Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + Deprecated::IStatusBar* statusBar, + const std::vector& displayTouches) + { + if (button == MouseButton_Left) + { + statusBar->SetMessage("selected thumbnail " + widget.GetName()); + std::string seriesId = widget.GetName().substr(strlen("thumbnail-series-")); + application_.SelectSeriesInMainViewport(seriesId); + } + return NULL; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/ThumbnailInteractor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/ThumbnailInteractor.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../../../Framework/Deprecated/Widgets/IWorldSceneInteractor.h" + +using namespace OrthancStone; + +namespace SimpleViewer { + + class SimpleViewerApplication; + + class ThumbnailInteractor : public Deprecated::IWorldSceneInteractor + { + private: + SimpleViewerApplication& application_; + public: + ThumbnailInteractor(SimpleViewerApplication& application) : + application_(application) + { + } + + virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + Deprecated::IStatusBar* statusBar, + const std::vector& displayTouches); + + virtual void MouseOver(CairoContext& context, + Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + double x, + double y, + Deprecated::IStatusBar* statusBar) + {} + + virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + {} + + virtual void KeyPressed(Deprecated::WorldSceneWidget& widget, + KeyboardKeys key, + char keyChar, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + {} + + }; + + +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,51 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "SimpleViewerWasmApplicationAdapter.h" + +namespace SimpleViewer +{ + + SimpleViewerWasmApplicationAdapter::SimpleViewerWasmApplicationAdapter(MessageBroker &broker, SimpleViewerApplication &application) + : WasmPlatformApplicationAdapter(broker, application), + viewerApplication_(application) + { + application.RegisterObserverCallback(new Callable(*this, &SimpleViewerWasmApplicationAdapter::OnStatusUpdated)); + } + + void SimpleViewerWasmApplicationAdapter::OnStatusUpdated(const SimpleViewerApplication::StatusUpdatedMessage &message) + { + Json::Value statusJson; + message.status_.ToJson(statusJson); + + Json::Value event; + event["event"] = "appStatusUpdated"; + event["data"] = statusJson; + + Json::StreamWriterBuilder builder; + std::unique_ptr writer(builder.newStreamWriter()); + std::ostringstream outputStr; + + writer->write(event, &outputStr); + + NotifyStatusUpdateFromCppToWebWithString(outputStr.str()); + } + +} // namespace SimpleViewer \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include +#include +#include + +#include "../../SimpleViewerApplication.h" + +namespace SimpleViewer { + + class SimpleViewerWasmApplicationAdapter : public WasmPlatformApplicationAdapter + { + SimpleViewerApplication& viewerApplication_; + + public: + SimpleViewerWasmApplicationAdapter(MessageBroker& broker, SimpleViewerApplication& application); + + private: + void OnStatusUpdated(const SimpleViewerApplication::StatusUpdatedMessage& message); + + }; + +} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Wasm/mainWasm.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Wasm/mainWasm.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "Platforms/Wasm/WasmWebService.h" +#include "Platforms/Wasm/WasmViewport.h" + +#include + +#include "../../SimpleViewerApplication.h" +#include "SimpleViewerWasmApplicationAdapter.h" + + +OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) { + + return new SimpleViewer::SimpleViewerApplication(broker); +} + +OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, IStoneApplication* application) +{ + return new SimpleViewer::SimpleViewerWasmApplicationAdapter(broker, *(dynamic_cast(application))); +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Wasm/simple-viewer.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Wasm/simple-viewer.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,43 @@ + + + + + + + + + + + + Simple Viewer + + + + +
+
+ +
+
+ +
+
+
+ + + + + + + + + +
+ + + + \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Wasm/simple-viewer.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Wasm/simple-viewer.ts Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,81 @@ +import wasmApplicationRunner = require('../../../../Platforms/Wasm/wasm-application-runner'); + +wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSimpleViewer", "/orthanc"); + +function SelectTool(toolName: string) { + var command = { + command: "selectTool:" + toolName, + commandType: "generic-no-arg-command", + args: { + } + }; + wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); +} + +function PerformAction(actionName: string) { + var command = { + command: "action:" + actionName, + commandType: "generic-no-arg-command", + args: { + } + }; + wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); +} + +class SimpleViewerUI { + + private _labelPatientId: HTMLSpanElement; + private _labelStudyDescription: HTMLSpanElement; + + public constructor() { + // install "SelectTool" handlers + document.querySelectorAll("[tool-selector]").forEach((e) => { + (e as HTMLButtonElement).addEventListener("click", () => { + SelectTool(e.attributes["tool-selector"].value); + }); + }); + + // install "PerformAction" handlers + document.querySelectorAll("[action-trigger]").forEach((e) => { + (e as HTMLButtonElement).addEventListener("click", () => { + PerformAction(e.attributes["action-trigger"].value); + }); + }); + + // connect all ui elements to members + this._labelPatientId = document.getElementById("label-patient-id") as HTMLSpanElement; + this._labelStudyDescription = document.getElementById("label-study-description") as HTMLSpanElement; + } + + public onAppStatusUpdated(status: any) { + this._labelPatientId.innerText = status["patientId"]; + this._labelStudyDescription.innerText = status["studyDescription"]; + // this.highlighThumbnail(status["currentInstanceIdInMainViewport"]); + } + +} + +var ui = new SimpleViewerUI(); + +// this method is called "from the C++ code" when the StoneApplication is updated. +// it can be used to update the UI of the application +function UpdateWebApplicationWithString(statusUpdateMessageString: string) { + console.log("updating web application with string: ", statusUpdateMessageString); + let statusUpdateMessage = JSON.parse(statusUpdateMessageString); + + if ("event" in statusUpdateMessage) { + let eventName = statusUpdateMessage["event"]; + if (eventName == "appStatusUpdated") { + ui.onAppStatusUpdated(statusUpdateMessage["data"]); + } + } +} + +function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) { + console.log("updating web application with serialized message: ", statusUpdateMessageString); + console.log(""); +} + +// make it available to other js scripts in the application +( window).UpdateWebApplicationWithString = UpdateWebApplicationWithString; +( window).UpdateWebApplicationWithSerializedMessage = UpdateWebApplicationWithSerializedMessage; diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Wasm/styles.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Wasm/styles.css Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,54 @@ +html, body { + width: 100%; + height: 100%; + margin: 0px; + border: 0; + overflow: hidden; /* Disable scrollbars */ + display: block; /* No floating content on sides */ + background-color: black; + color: white; + font-family: Arial, Helvetica, sans-serif; +} + +canvas { + left:0px; + top:0px; +} + +#canvas-group { + padding:5px; + background-color: grey; +} + +#status-group { + padding:5px; +} + +#worklist-group { + padding:5px; +} + +.vsol-button { + height: 40px; +} + +#thumbnails-group ul li { + display: inline; + list-style: none; +} + +.thumbnail { + width: 100px; + height: 100px; + padding: 3px; +} + +.thumbnail-selected { + border-width: 1px; + border-color: red; + border-style: solid; +} + +#template-thumbnail-li { + display: none !important; +} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewer/Wasm/tsconfig-simple-viewer.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewer/Wasm/tsconfig-simple-viewer.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,9 @@ +{ + "extends" : "../../Web/tsconfig-samples", + "compilerOptions": { + }, + "include" : [ + "simple-viewer.ts", + "../../build-wasm/ApplicationCommands_generated.ts" + ] +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SimpleViewerApplicationSingleFile.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SimpleViewerApplicationSingleFile.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,461 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleApplicationBase.h" + +#include "../../../Framework/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" +#include "../../../Platforms/Wasm/Defaults.h" +#endif + +#include +#include + +namespace OrthancStone +{ + namespace Samples + { + class SimpleViewerApplication : + public SampleSingleCanvasWithButtonsApplicationBase, + public ObserverBase + { + private: + class ThumbnailInteractor : public Deprecated::IWorldSceneInteractor + { + private: + SimpleViewerApplication& application_; + + public: + ThumbnailInteractor(SimpleViewerApplication& application) : + application_(application) + { + } + + virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + Deprecated::IStatusBar* statusBar, + const std::vector& displayTouches) + { + if (button == MouseButton_Left) + { + statusBar->SetMessage("selected thumbnail " + widget.GetName()); + std::string seriesId = widget.GetName().substr(strlen("thumbnail-series-")); + application_.SelectSeriesInMainViewport(seriesId); + } + return NULL; + } + + virtual void MouseOver(CairoContext& context, + Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + double x, + double y, + Deprecated::IStatusBar* statusBar) + { + } + + virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + { + } + + virtual void KeyPressed(Deprecated::WorldSceneWidget& widget, + KeyboardKeys key, + char keyChar, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + { + } + }; + + class MainWidgetInteractor : public Deprecated::IWorldSceneInteractor + { + private: + SimpleViewerApplication& application_; + + public: + MainWidgetInteractor(SimpleViewerApplication& application) : + application_(application) + { + } + + virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + Deprecated::IStatusBar* statusBar, + const std::vector& displayTouches) + { + if (button == MouseButton_Left) + { + if (application_.currentTool_ == Tool_LineMeasure) + { + return new Deprecated::LineMeasureTracker(statusBar, dynamic_cast(widget).GetSlice(), + x, y, 255, 0, 0, application_.GetFont()); + } + else if (application_.currentTool_ == Tool_CircleMeasure) + { + return new Deprecated::CircleMeasureTracker(statusBar, dynamic_cast(widget).GetSlice(), + x, y, 255, 0, 0, application_.GetFont()); + } + } + return NULL; + } + + virtual void MouseOver(CairoContext& context, + Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + double x, + double y, + Deprecated::IStatusBar* statusBar) + { + if (statusBar != NULL) + { + Vector p = dynamic_cast(widget).GetSlice().MapSliceToWorldCoordinates(x, y); + + char buf[64]; + sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", + p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); + statusBar->SetMessage(buf); + } + } + + virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + { + } + + virtual void KeyPressed(Deprecated::WorldSceneWidget& widget, + KeyboardKeys key, + char keyChar, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + { + switch (keyChar) + { + case 's': + widget.FitContent(); + break; + + case 'l': + application_.currentTool_ = Tool_LineMeasure; + break; + + case 'c': + application_.currentTool_ = Tool_CircleMeasure; + break; + + default: + break; + } + } + }; + + +#if ORTHANC_ENABLE_WASM==1 + class SimpleViewerApplicationAdapter : public WasmPlatformApplicationAdapter + { + SimpleViewerApplication& viewerApplication_; + + public: + SimpleViewerApplicationAdapter(SimpleViewerApplication& application) + : WasmPlatformApplicationAdapter(application), + viewerApplication_(application) + { + } + + virtual void HandleSerializedMessageFromWeb(std::string& output, const std::string& input) + { + if (input == "select-tool:line-measure") + { + viewerApplication_.currentTool_ = Tool_LineMeasure; + NotifyStatusUpdateFromCppToWebWithString("currentTool=line-measure"); + } + else if (input == "select-tool:circle-measure") + { + viewerApplication_.currentTool_ = Tool_CircleMeasure; + NotifyStatusUpdateFromCppToWebWithString("currentTool=circle-measure"); + } + + output = "ok"; + } + + virtual void NotifySerializedMessageFromCppToWeb(const std::string& statusUpdateMessage) + { + UpdateStoneApplicationStatusFromCppWithSerializedMessage(statusUpdateMessage.c_str()); + } + + virtual void NotifyStatusUpdateFromCppToWebWithString(const std::string& statusUpdateMessage) + { + UpdateStoneApplicationStatusFromCppWithString(statusUpdateMessage.c_str()); + } + + }; +#endif + enum Tool { + Tool_LineMeasure, + Tool_CircleMeasure + }; + + Tool currentTool_; + std::unique_ptr mainWidgetInteractor_; + std::unique_ptr thumbnailInteractor_; + Deprecated::LayoutWidget* mainLayout_; + Deprecated::LayoutWidget* thumbnailsLayout_; + std::vector > thumbnails_; + + std::map > instancesIdsPerSeriesId_; + std::map seriesTags_; + + unsigned int currentInstanceIndex_; + Deprecated::WidgetViewport* wasmViewport1_; + Deprecated::WidgetViewport* wasmViewport2_; + + Deprecated::IStatusBar* statusBar_; + std::unique_ptr smartLoader_; + + Orthanc::Font font_; + + public: + SimpleViewerApplication() : + currentTool_(Tool_LineMeasure), + mainLayout_(NULL), + currentInstanceIndex_(0), + wasmViewport1_(NULL), + wasmViewport2_(NULL) + { + font_.LoadFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); +// DeclareIgnoredMessage(MessageType_Widget_ContentChanged); + } + + virtual void DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("studyId", boost::program_options::value(), + "Orthanc ID of the study") + ; + + options.add(generic); + } + + virtual void Initialize(StoneApplicationContext* context, + Deprecated::IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + context_ = context; + statusBar_ = &statusBar; + + {// initialize viewports and layout + mainLayout_ = new Deprecated::LayoutWidget("main-layout"); + mainLayout_->SetPadding(10); + mainLayout_->SetBackgroundCleared(true); + mainLayout_->SetBackgroundColor(0, 0, 0); + mainLayout_->SetHorizontal(); + + boost::shared_ptr thumbnailsLayout_(new Deprecated::LayoutWidget("thumbnail-layout")); + thumbnailsLayout_->SetPadding(10); + thumbnailsLayout_->SetBackgroundCleared(true); + thumbnailsLayout_->SetBackgroundColor(50, 50, 50); + thumbnailsLayout_->SetVertical(); + + boost::shared_ptr widget + (new Deprecated::SliceViewerWidget("main-viewport")); + SetCentralWidget(widget); + //mainWidget_->RegisterObserver(*this); + + // hierarchy + mainLayout_->AddWidget(thumbnailsLayout_); + mainLayout_->AddWidget(widget); + + // sources + smartLoader_.reset(new Deprecated::SmartLoader(context->GetOrthancApiClient())); + smartLoader_->SetImageQuality(Deprecated::SliceImageQuality_FullPam); + + mainLayout_->SetTransmitMouseOver(true); + mainWidgetInteractor_.reset(new MainWidgetInteractor(*this)); + widget->SetInteractor(*mainWidgetInteractor_); + thumbnailInteractor_.reset(new ThumbnailInteractor(*this)); + } + + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); + statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport"); + + + if (parameters.count("studyId") < 1) + { + LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc"; + context->GetOrthancApiClient()->GetJsonAsync( + "/studies", + new Deprecated::DeprecatedCallable + (GetSharedObserver(), &SimpleViewerApplication::OnStudyListReceived)); + } + else + { + SelectStudy(parameters["studyId"].as()); + } + } + + void OnStudyListReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) + { + const Json::Value& response = message.GetJson(); + + if (response.isArray() && + response.size() >= 1) + { + SelectStudy(response[0].asString()); + } + } + + void OnStudyReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) + { + const Json::Value& response = message.GetJson(); + + if (response.isObject() && response["Series"].isArray()) + { + for (size_t i=0; i < response["Series"].size(); i++) + { + context_->GetOrthancApiClient()->GetJsonAsync( + "/series/" + response["Series"][(int)i].asString(), + new Deprecated::DeprecatedCallable + (GetSharedObserver(), &SimpleViewerApplication::OnSeriesReceived)); + } + } + } + + void OnSeriesReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) + { + const Json::Value& response = message.GetJson(); + + if (response.isObject() && + response["Instances"].isArray() && + response["Instances"].size() > 0) + { + // keep track of all instances IDs + const std::string& seriesId = response["ID"].asString(); + seriesTags_[seriesId] = response; + instancesIdsPerSeriesId_[seriesId] = std::vector(); + for (size_t i = 0; i < response["Instances"].size(); i++) + { + const std::string& instanceId = response["Instances"][static_cast(i)].asString(); + instancesIdsPerSeriesId_[seriesId].push_back(instanceId); + } + + // load the first instance in the thumbnail + LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]); + + // if this is the first thumbnail loaded, load the first instance in the mainWidget + Deprecated::SliceViewerWidget& widget = dynamic_cast(*GetCentralWidget()); + if (widget.GetLayerCount() == 0) + { + smartLoader_->SetFrameInWidget(widget, 0, instancesIdsPerSeriesId_[seriesId][0], 0); + } + } + } + + void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId) + { + LOG(INFO) << "Loading thumbnail for series " << seriesId; + boost::shared_ptr thumbnailWidget(new Deprecated::SliceViewerWidget("thumbnail-series-" + seriesId)); + thumbnails_.push_back(thumbnailWidget); + thumbnailsLayout_->AddWidget(thumbnailWidget); + Register(*thumbnailWidget, &SimpleViewerApplication::OnWidgetGeometryChanged); + smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0); + thumbnailWidget->SetInteractor(*thumbnailInteractor_); + } + + void SelectStudy(const std::string& studyId) + { + LOG(INFO) << "Selecting study: " << studyId; + context_->GetOrthancApiClient()->GetJsonAsync( + "/studies/" + studyId, new Deprecated::DeprecatedCallable + (GetSharedObserver(), &SimpleViewerApplication::OnStudyReceived)); + } + + void OnWidgetGeometryChanged(const Deprecated::SliceViewerWidget::GeometryChangedMessage& message) + { + // TODO: The "const_cast" could probably be replaced by "mainWidget" + const_cast(message.GetOrigin()).FitContent(); + } + + void SelectSeriesInMainViewport(const std::string& seriesId) + { + Deprecated::SliceViewerWidget& widget = dynamic_cast(*GetCentralWidget()); + smartLoader_->SetFrameInWidget(widget, 0, instancesIdsPerSeriesId_[seriesId][0], 0); + } + + const Orthanc::Font& GetFont() const + { + return font_; + } + + virtual void OnPushButton1Clicked() {} + virtual void OnPushButton2Clicked() {} + virtual void OnTool1Clicked() { currentTool_ = Tool_LineMeasure;} + virtual void OnTool2Clicked() { currentTool_ = Tool_CircleMeasure;} + + virtual void GetButtonNames(std::string& pushButton1, + std::string& pushButton2, + std::string& tool1, + std::string& tool2) + { + tool1 = "line"; + tool2 = "circle"; + pushButton1 = "action1"; + pushButton2 = "action2"; + } + +#if ORTHANC_ENABLE_WASM==1 + virtual void InitializeWasm() + { + AttachWidgetToWasmViewport("canvas", thumbnailsLayout_); + AttachWidgetToWasmViewport("canvas2", widget); + } +#endif + + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SingleFrameApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SingleFrameApplication.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleApplicationBase.h" + +#include "../../../Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h" +#include "../../../Framework/Deprecated/Widgets/SliceViewerWidget.h" + +#include +#include + +#include + + +namespace OrthancStone +{ + namespace Samples + { + class SingleFrameApplication : + public SampleSingleCanvasApplicationBase, + public ObserverBase + { + private: + class Interactor : public Deprecated::IWorldSceneInteractor + { + private: + SingleFrameApplication& application_; + + public: + Interactor(SingleFrameApplication& application) : + application_(application) + { + } + + virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + Deprecated::IStatusBar* statusBar, + const std::vector& displayTouches) + { + return NULL; + } + + virtual void MouseOver(CairoContext& context, + Deprecated::WorldSceneWidget& widget, + const Deprecated::ViewportGeometry& view, + double x, + double y, + Deprecated::IStatusBar* statusBar) + { + if (statusBar != NULL) + { + Vector p = dynamic_cast(widget).GetSlice().MapSliceToWorldCoordinates(x, y); + + char buf[64]; + sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", + p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); + statusBar->SetMessage(buf); + } + } + + virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + { + int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1); + + switch (direction) + { + case MouseWheelDirection_Up: + application_.OffsetSlice(-scale); + break; + + case MouseWheelDirection_Down: + application_.OffsetSlice(scale); + break; + + default: + break; + } + } + + virtual void KeyPressed(Deprecated::WorldSceneWidget& widget, + KeyboardKeys key, + char keyChar, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + { + switch (keyChar) + { + case 's': + widget.FitContent(); + break; + + default: + break; + } + } + }; + + + void OffsetSlice(int offset) + { + if (source_) + { + int slice = static_cast(slice_) + offset; + + if (slice < 0) + { + slice = 0; + } + + if (slice >= static_cast(source_->GetSlicesCount())) + { + slice = static_cast(source_->GetSlicesCount()) - 1; + } + + if (slice != static_cast(slice_)) + { + SetSlice(slice); + } + } + } + + + void SetSlice(size_t index) + { + if (source_ && + index < source_->GetSlicesCount()) + { + slice_ = static_cast(index); + +#if 1 + widget_->SetSlice(source_->GetSlice(slice_).GetGeometry()); +#else + // TEST for scene extents - Rotate the axes + double a = 15.0 / 180.0 * boost::math::constants::pi(); + +#if 1 + Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); + Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0); +#else + // Flip the normal + Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); + Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0); +#endif + + SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y); + widget_->SetSlice(s); +#endif + } + } + + + void OnMainWidgetGeometryReady(const Deprecated::IVolumeSlicer::GeometryReadyMessage& message) + { + // Once the geometry of the series is downloaded from Orthanc, + // display its middle slice, and adapt the viewport to fit this + // slice + if (source_ && + source_.get() == &message.GetOrigin()) + { + SetSlice(source_->GetSlicesCount() / 2); + } + + widget_->FitContent(); + } + + boost::shared_ptr widget_; + std::unique_ptr mainWidgetInteractor_; + boost::shared_ptr source_; + unsigned int slice_; + + public: + SingleFrameApplication() : + slice_(0) + { + } + + virtual void DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("instance", boost::program_options::value(), + "Orthanc ID of the instance") + ("frame", boost::program_options::value()->default_value(0), + "Number of the frame, for multi-frame DICOM instances") + ("smooth", boost::program_options::value()->default_value(true), + "Enable bilinear interpolation to smooth the image") + ; + + options.add(generic); + } + + virtual void Initialize(StoneApplicationContext* context, + Deprecated::IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + context_ = context; + + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); + + if (parameters.count("instance") != 1) + { + LOG(ERROR) << "The instance ID is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::string instance = parameters["instance"].as(); + int frame = parameters["frame"].as(); + + widget_.reset(new Deprecated::SliceViewerWidget("main-widget")); + SetCentralWidget(widget_); + + boost::shared_ptr layer(new Deprecated::DicomSeriesVolumeSlicer); + layer->Connect(context->GetOrthancApiClient()); + source_ = layer; + + layer->LoadFrame(instance, frame); + Register(*layer, &SingleFrameApplication::OnMainWidgetGeometryReady); + widget_->AddLayer(layer); + + Deprecated::RenderStyle s; + + if (parameters["smooth"].as()) + { + s.interpolation_ = ImageInterpolation_Bilinear; + } + + widget_->SetLayerStyle(0, s); + widget_->SetTransmitMouseOver(true); + + mainWidgetInteractor_.reset(new Interactor(*this)); + widget_->SetInteractor(*mainWidgetInteractor_); + } + }; + + + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SingleFrameEditorApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SingleFrameEditorApplication.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,531 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleApplicationBase.h" + +#include "../../../Framework/Radiography/RadiographyLayerCropTracker.h" +#include "../../../Framework/Radiography/RadiographyLayerMaskTracker.h" +#include "../../../Framework/Radiography/RadiographyLayerMoveTracker.h" +#include "../../../Framework/Radiography/RadiographyLayerResizeTracker.h" +#include "../../../Framework/Radiography/RadiographyLayerRotateTracker.h" +#include "../../../Framework/Radiography/RadiographyMaskLayer.h" +#include "../../../Framework/Radiography/RadiographyScene.h" +#include "../../../Framework/Radiography/RadiographySceneCommand.h" +#include "../../../Framework/Radiography/RadiographySceneReader.h" +#include "../../../Framework/Radiography/RadiographySceneWriter.h" +#include "../../../Framework/Radiography/RadiographyWidget.h" +#include "../../../Framework/Radiography/RadiographyWindowingTracker.h" +#include "../../../Framework/Toolbox/TextRenderer.h" + +#include +#include +#include +#include +#include + + +// Export using PAM is faster than using PNG, but requires Orthanc +// core >= 1.4.3 +#define EXPORT_USING_PAM 1 + + +namespace OrthancStone +{ + namespace Samples + { + class RadiographyEditorInteractor : + public Deprecated::IWorldSceneInteractor, + public ObserverBase + { + private: + enum Tool + { + Tool_Move, + Tool_Rotate, + Tool_Crop, + Tool_Resize, + Tool_Mask, + Tool_Windowing + }; + + + StoneApplicationContext* context_; + UndoRedoStack undoRedoStack_; + Tool tool_; + RadiographyMaskLayer* maskLayer_; + + + static double GetHandleSize() + { + return 10.0; + } + + + public: + RadiographyEditorInteractor() : + context_(NULL), + tool_(Tool_Move), + maskLayer_(NULL) + { + } + + void SetContext(StoneApplicationContext& context) + { + context_ = &context; + } + + void SetMaskLayer(RadiographyMaskLayer* maskLayer) + { + maskLayer_ = maskLayer; + } + virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& worldWidget, + const Deprecated::ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + Deprecated::IStatusBar* statusBar, + const std::vector& displayTouches) + { + RadiographyWidget& widget = dynamic_cast(worldWidget); + + if (button == MouseButton_Left) + { + size_t selected; + + if (tool_ == Tool_Windowing) + { + return new RadiographyWindowingTracker( + undoRedoStack_, + widget.GetScene(), + widget, + OrthancStone::ImageInterpolation_Nearest, + viewportX, viewportY, + RadiographyWindowingTracker::Action_DecreaseWidth, + RadiographyWindowingTracker::Action_IncreaseWidth, + RadiographyWindowingTracker::Action_DecreaseCenter, + RadiographyWindowingTracker::Action_IncreaseCenter); + } + else if (!widget.LookupSelectedLayer(selected)) + { + // No layer is currently selected + size_t layer; + if (widget.GetScene().LookupLayer(layer, x, y)) + { + widget.Select(layer); + } + + return NULL; + } + else if (tool_ == Tool_Crop || + tool_ == Tool_Resize || + tool_ == Tool_Mask) + { + RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected); + + ControlPoint controlPoint; + if (accessor.GetLayer().LookupControlPoint(controlPoint, x, y, view.GetZoom(), GetHandleSize())) + { + switch (tool_) + { + case Tool_Crop: + return new RadiographyLayerCropTracker + (undoRedoStack_, widget.GetScene(), view, selected, controlPoint); + + case Tool_Mask: + return new RadiographyLayerMaskTracker + (undoRedoStack_, widget.GetScene(), view, selected, controlPoint); + + case Tool_Resize: + return new RadiographyLayerResizeTracker + (undoRedoStack_, widget.GetScene(), selected, controlPoint, + (modifiers & KeyboardModifiers_Shift)); + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + else + { + size_t layer; + + if (widget.GetScene().LookupLayer(layer, x, y)) + { + widget.Select(layer); + } + else + { + widget.Unselect(); + } + + return NULL; + } + } + else + { + size_t layer; + + if (widget.GetScene().LookupLayer(layer, x, y)) + { + if (layer == selected) + { + switch (tool_) + { + case Tool_Move: + return new RadiographyLayerMoveTracker + (undoRedoStack_, widget.GetScene(), layer, x, y, + (modifiers & KeyboardModifiers_Shift)); + + case Tool_Rotate: + return new RadiographyLayerRotateTracker + (undoRedoStack_, widget.GetScene(), view, layer, x, y, + (modifiers & KeyboardModifiers_Shift)); + + default: + break; + } + + return NULL; + } + else + { + widget.Select(layer); + return NULL; + } + } + else + { + widget.Unselect(); + return NULL; + } + } + } + else + { + return NULL; + } + return NULL; + } + + virtual void MouseOver(CairoContext& context, + Deprecated::WorldSceneWidget& worldWidget, + const Deprecated::ViewportGeometry& view, + double x, + double y, + Deprecated::IStatusBar* statusBar) + { + RadiographyWidget& widget = dynamic_cast(worldWidget); + +#if 0 + if (statusBar != NULL) + { + char buf[64]; + sprintf(buf, "X = %.02f Y = %.02f (in cm)", x / 10.0, y / 10.0); + statusBar->SetMessage(buf); + } +#endif + + size_t selected; + + if (widget.LookupSelectedLayer(selected) && + (tool_ == Tool_Crop || + tool_ == Tool_Resize || + tool_ == Tool_Mask)) + { + RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected); + + ControlPoint controlPoint; + if (accessor.GetLayer().LookupControlPoint(controlPoint, x, y, view.GetZoom(), GetHandleSize())) + { + double z = 1.0 / view.GetZoom(); + + context.SetSourceColor(255, 0, 0); + cairo_t* cr = context.GetObject(); + cairo_set_line_width(cr, 2.0 * z); + cairo_move_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y - GetHandleSize() * z); + cairo_line_to(cr, controlPoint.x + GetHandleSize() * z, controlPoint.y - GetHandleSize() * z); + cairo_line_to(cr, controlPoint.x + GetHandleSize() * z, controlPoint.y + GetHandleSize() * z); + cairo_line_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y + GetHandleSize() * z); + cairo_line_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y - GetHandleSize() * z); + cairo_stroke(cr); + } + } + } + + virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + { + } + + virtual void KeyPressed(Deprecated::WorldSceneWidget& worldWidget, + KeyboardKeys key, + char keyChar, + KeyboardModifiers modifiers, + Deprecated::IStatusBar* statusBar) + { + RadiographyWidget& widget = dynamic_cast(worldWidget); + + switch (keyChar) + { + case 'a': + widget.FitContent(); + break; + + case 'c': + tool_ = Tool_Crop; + break; + + case 'm': + tool_ = Tool_Mask; + widget.Select(1); + break; + + case 'd': + { + // dump to json and reload + Json::Value snapshot; + RadiographySceneWriter writer; + writer.Write(snapshot, widget.GetScene()); + + LOG(INFO) << "JSON export was successful: " + << snapshot.toStyledString(); + + boost::shared_ptr scene(new RadiographyScene); + RadiographySceneReader reader(*scene, *context_->GetOrthancApiClient()); + reader.Read(snapshot); + + widget.SetScene(scene); + };break; + + case 'e': + { + Orthanc::DicomMap tags; + + // Minimal set of tags to generate a valid CR image + tags.SetValue(Orthanc::DICOM_TAG_ACCESSION_NUMBER, "NOPE", false); + tags.SetValue(Orthanc::DICOM_TAG_BODY_PART_EXAMINED, "PELVIS", false); + tags.SetValue(Orthanc::DICOM_TAG_INSTANCE_NUMBER, "1", false); + //tags.SetValue(Orthanc::DICOM_TAG_LATERALITY, "", false); + tags.SetValue(Orthanc::DICOM_TAG_MANUFACTURER, "OSIMIS", false); + tags.SetValue(Orthanc::DICOM_TAG_MODALITY, "CR", false); + tags.SetValue(Orthanc::DICOM_TAG_PATIENT_BIRTH_DATE, "20000101", false); + tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ID, "hello", false); + tags.SetValue(Orthanc::DICOM_TAG_PATIENT_NAME, "HELLO^WORLD", false); + tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ORIENTATION, "", false); + tags.SetValue(Orthanc::DICOM_TAG_PATIENT_SEX, "M", false); + tags.SetValue(Orthanc::DICOM_TAG_REFERRING_PHYSICIAN_NAME, "HOUSE^MD", false); + tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false); + tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false); + tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false); + tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false); + + if (context_ != NULL) + { + widget.GetScene().ExportDicom(*context_->GetOrthancApiClient(), + tags, std::string(), 0.1, 0.1, widget.IsInverted(), + false /* autoCrop */, widget.GetInterpolation(), EXPORT_USING_PAM); + } + + break; + } + + case 'i': + widget.SwitchInvert(); + break; + + case 't': + tool_ = Tool_Move; + break; + + case 'n': + { + switch (widget.GetInterpolation()) + { + case ImageInterpolation_Nearest: + LOG(INFO) << "Switching to bilinear interpolation"; + widget.SetInterpolation(ImageInterpolation_Bilinear); + break; + + case ImageInterpolation_Bilinear: + LOG(INFO) << "Switching to nearest neighbor interpolation"; + widget.SetInterpolation(ImageInterpolation_Nearest); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + break; + } + + case 'r': + tool_ = Tool_Rotate; + break; + + case 's': + tool_ = Tool_Resize; + break; + + case 'w': + tool_ = Tool_Windowing; + break; + + case 'y': + if (modifiers & KeyboardModifiers_Control) + { + undoRedoStack_.Redo(); + widget.NotifyContentChanged(); + } + break; + + case 'z': + if (modifiers & KeyboardModifiers_Control) + { + undoRedoStack_.Undo(); + widget.NotifyContentChanged(); + } + break; + + default: + break; + } + } + }; + + + + class SingleFrameEditorApplication : + public SampleSingleCanvasApplicationBase, + public IObserver + { + private: + boost::shared_ptr scene_; + RadiographyEditorInteractor interactor_; + RadiographyMaskLayer* maskLayer_; + + public: + virtual ~SingleFrameEditorApplication() + { + LOG(WARNING) << "Destroying the application"; + } + + virtual void DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("instance", boost::program_options::value(), + "Orthanc ID of the instance") + ("frame", boost::program_options::value()->default_value(0), + "Number of the frame, for multi-frame DICOM instances") + ; + + options.add(generic); + } + + virtual void Initialize(StoneApplicationContext* context, + Deprecated::IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + context_ = context; + interactor_.SetContext(*context); + + statusBar.SetMessage("Use the key \"a\" to reinitialize the layout"); + statusBar.SetMessage("Use the key \"c\" to crop"); + statusBar.SetMessage("Use the key \"e\" to export DICOM to the Orthanc server"); + statusBar.SetMessage("Use the key \"f\" to switch full screen"); + statusBar.SetMessage("Use the key \"i\" to invert contrast"); + statusBar.SetMessage("Use the key \"m\" to modify the mask"); + statusBar.SetMessage("Use the key \"n\" to switch between nearest neighbor and bilinear interpolation"); + statusBar.SetMessage("Use the key \"r\" to rotate objects"); + statusBar.SetMessage("Use the key \"s\" to resize objects (not applicable to DICOM layers)"); + statusBar.SetMessage("Use the key \"t\" to move (translate) objects"); + statusBar.SetMessage("Use the key \"w\" to change windowing"); + + statusBar.SetMessage("Use the key \"ctrl-z\" to undo action"); + statusBar.SetMessage("Use the key \"ctrl-y\" to redo action"); + + if (parameters.count("instance") != 1) + { + LOG(ERROR) << "The instance ID is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::string instance = parameters["instance"].as(); + //int frame = parameters["frame"].as(); + + scene_.reset(new RadiographyScene); + + RadiographyLayer& dicomLayer = scene_->LoadDicomFrame(*context->GetOrthancApiClient(), instance, 0, false, NULL); + //scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0); + // = scene_->LoadDicomFrame(context->GetOrthancApiClient(), "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false, NULL); + +#if !defined(ORTHANC_ENABLE_WASM) || ORTHANC_ENABLE_WASM != 1 + Orthanc::HttpClient::ConfigureSsl(true, "/etc/ssl/certs/ca-certificates.crt"); +#endif + + //scene_->LoadDicomWebFrame(context->GetWebService()); + + std::vector mask; + mask.push_back(Orthanc::ImageProcessing::ImagePoint(1100, 100)); + mask.push_back(Orthanc::ImageProcessing::ImagePoint(1100, 1000)); + mask.push_back(Orthanc::ImageProcessing::ImagePoint(2000, 1000)); + mask.push_back(Orthanc::ImageProcessing::ImagePoint(2200, 150)); + mask.push_back(Orthanc::ImageProcessing::ImagePoint(1500, 550)); + maskLayer_ = dynamic_cast(&(scene_->LoadMask(mask, dynamic_cast(dicomLayer), 128.0f, NULL))); + interactor_.SetMaskLayer(maskLayer_); + + { + std::unique_ptr renderedTextAlpha(TextRenderer::Render(Orthanc::EmbeddedResources::UBUNTU_FONT, 100, + "%öÇaA&#")); + RadiographyLayer& layer = scene_->LoadAlphaBitmap(renderedTextAlpha.release(), NULL); + dynamic_cast(layer).SetForegroundValue(200.0f * 256.0f); + } + + { + RadiographyTextLayer::RegisterFont("ubuntu", Orthanc::EmbeddedResources::UBUNTU_FONT); + RadiographyLayer& layer = scene_->LoadText("Hello\nworld", "ubuntu", 20, 128, NULL, false); + layer.SetResizeable(true); + } + + { + RadiographyLayer& layer = scene_->LoadTestBlock(100, 50, NULL); + layer.SetResizeable(true); + layer.SetPan(0, 200); + } + + boost::shared_ptr widget(new RadiographyWidget(scene_, "main-widget")); + widget->SetTransmitMouseOver(true); + widget->SetInteractor(interactor_); + SetCentralWidget(widget); + + //scene_->SetWindowing(128, 256); + } + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SingleVolumeApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SingleVolumeApplication.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,277 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleApplicationBase.h" +#include "../../../Framework/dev.h" +#include "../../../Framework/Layers/LineMeasureTracker.h" +#include "../../../Framework/Layers/CircleMeasureTracker.h" + +#include +#include + +#include // TODO REMOVE +#include "../../../Framework/Layers/DicomStructureSetSlicer.h" // TODO REMOVE +#include "../../../Framework/Toolbox/MessagingToolbox.h" // TODO REMOVE + +namespace OrthancStone +{ + namespace Samples + { + class SingleVolumeApplication : public SampleApplicationBase + { + private: + class Interactor : public VolumeImageInteractor + { + private: + SliceViewerWidget& widget_; + size_t layer_; + + protected: + virtual void NotifySliceContentChange(const ISlicedVolume& volume, + const size_t& sliceIndex, + const Slice& slice) + { + const OrthancVolumeImage& image = dynamic_cast(volume); + + RenderStyle s = widget_.GetLayerStyle(layer_); + + if (image.FitWindowingToRange(s, slice.GetConverter())) + { + //printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); + widget_.SetLayerStyle(layer_, s); + } + } + + virtual void MouseOver(CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) + { + const SliceViewerWidget& w = dynamic_cast(widget); + Vector p = w.GetSlice().MapSliceToWorldCoordinates(x, y); + printf("%f %f %f\n", p[0], p[1], p[2]); + } + + public: + Interactor(OrthancVolumeImage& volume, + SliceViewerWidget& widget, + VolumeProjection projection, + size_t layer) : + VolumeImageInteractor(volume, widget, projection), + widget_(widget), + layer_(layer) + { + } + }; + + + public: + virtual void DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("series", boost::program_options::value(), + "Orthanc ID of the series") + ("instance", boost::program_options::value(), + "Orthanc ID of a multi-frame instance that describes a 3D volume") + ("threads", boost::program_options::value()->default_value(3), + "Number of download threads") + ("projection", boost::program_options::value()->default_value("axial"), + "Projection of interest (can be axial, sagittal or coronal)") + ("reverse", boost::program_options::value()->default_value(false), + "Reverse the normal direction of the volume") + ; + + options.add(generic); + } + + virtual void Initialize(IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + if (parameters.count("series") > 1 || + parameters.count("instance") > 1) + { + LOG(ERROR) << "Only one series or instance is allowed"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (parameters.count("series") == 1 && + parameters.count("instance") == 1) + { + LOG(ERROR) << "Cannot specify both a series and an instance"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::string series; + if (parameters.count("series") == 1) + { + series = parameters["series"].as(); + } + + std::string instance; + if (parameters.count("instance") == 1) + { + instance = parameters["instance"].as(); + } + + if (series.empty() && + instance.empty()) + { + LOG(ERROR) << "The series ID or instance ID is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + //unsigned int threads = parameters["threads"].as(); + //bool reverse = parameters["reverse"].as(); + + std::string tmp = parameters["projection"].as(); + Orthanc::Toolbox::ToLowerCase(tmp); + + VolumeProjection projection; + if (tmp == "axial") + { + projection = VolumeProjection_Axial; + } + else if (tmp == "sagittal") + { + projection = VolumeProjection_Sagittal; + } + else if (tmp == "coronal") + { + projection = VolumeProjection_Coronal; + } + else + { + LOG(ERROR) << "Unknown projection: " << tmp; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::unique_ptr widget(new SliceViewerWidget); + +#if 1 + std::unique_ptr volume(new OrthancVolumeImage(context.GetWebService(), true)); + if (series.empty()) + { + volume->ScheduleLoadInstance(instance); + } + else + { + volume->ScheduleLoadSeries(series); + } + + widget->AddLayer(new VolumeImageMPRSlicer(*volume)); + + context_->AddInteractor(new Interactor(*volume, *widget, projection, 0)); + context_->AddSlicedVolume(volume.release()); + + if (1) + { + RenderStyle s; + //s.drawGrid_ = true; + s.alpha_ = 1; + s.windowing_ = ImageWindowing_Bone; + widget->SetLayerStyle(0, s); + } + else + { + RenderStyle s; + s.alpha_ = 1; + s.applyLut_ = true; + s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; + s.interpolation_ = ImageInterpolation_Bilinear; + widget->SetLayerStyle(0, s); + } +#else + std::unique_ptr ct(new OrthancVolumeImage(context_->GetWebService(), false)); + //ct->ScheduleLoadSeries("15a6f44a-ac7b88fe-19c462d9-dddd918e-b01550d8"); // 0178023P + //ct->ScheduleLoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d"); + //ct->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // IBA + //ct->ScheduleLoadSeries("03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); // 0522c0001 TCIA + ct->ScheduleLoadSeries("295e8a13-dfed1320-ba6aebb2-9a13e20f-1b3eb953"); // Captain + + std::unique_ptr pet(new OrthancVolumeImage(context_->GetWebService(), true)); + //pet->ScheduleLoadSeries("48d2997f-8e25cd81-dd715b64-bd79cdcc-e8fcee53"); // 0178023P + //pet->ScheduleLoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e"); + //pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1 + //pet->ScheduleLoadInstance("337876a1-a68a9718-f15abccd-38faafa1-b99b496a"); // IBA 2 + //pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 3 + //pet->ScheduleLoadInstance("269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c"); // 0522c0001 TCIA + pet->ScheduleLoadInstance("f080888c-0ab7528a-f7d9c28c-84980eb1-ff3b0ae6"); // Captain 1 + //pet->ScheduleLoadInstance("4f78055b-6499a2c5-1e089290-394acc05-3ec781c1"); // Captain 2 + + std::unique_ptr rtStruct(new StructureSetLoader(context_->GetWebService())); + //rtStruct->ScheduleLoadInstance("c2ebc17b-6b3548db-5e5da170-b8ecab71-ea03add3"); // 0178023P + //rtStruct->ScheduleLoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // IBA + //rtStruct->ScheduleLoadInstance("17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA + rtStruct->ScheduleLoadInstance("96c889ab-29fe5c54-dda6e66c-3949e4da-58f90d75"); // Captain + + widget->AddLayer(new VolumeImageMPRSlicer(*ct)); + widget->AddLayer(new VolumeImageMPRSlicer(*pet)); + widget->AddLayer(new DicomStructureSetSlicer(*rtStruct)); + + context_->AddInteractor(new Interactor(*pet, *widget, projection, 1)); + //context_->AddInteractor(new VolumeImageInteractor(*ct, *widget, projection)); + + context_->AddSlicedVolume(ct.release()); + context_->AddSlicedVolume(pet.release()); + context_->AddVolumeLoader(rtStruct.release()); + + { + RenderStyle s; + //s.drawGrid_ = true; + s.alpha_ = 1; + s.windowing_ = ImageWindowing_Bone; + widget->SetLayerStyle(0, s); + } + + { + RenderStyle s; + //s.drawGrid_ = true; + s.SetColor(255, 0, 0); // Draw missing PET layer in red + s.alpha_ = 0.5; + s.applyLut_ = true; + s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; + s.interpolation_ = ImageInterpolation_Bilinear; + s.windowing_ = ImageWindowing_Custom; + s.customWindowCenter_ = 0; + s.customWindowWidth_ = 128; + widget->SetLayerStyle(1, s); + } +#endif + + + statusBar.SetMessage("Use the keys \"b\", \"l\" and \"d\" to change Hounsfield windowing"); + statusBar.SetMessage("Use the keys \"t\" to track the (X,Y,Z) mouse coordinates"); + statusBar.SetMessage("Use the keys \"m\" to measure distances"); + statusBar.SetMessage("Use the keys \"c\" to draw circles"); + + widget->SetTransmitMouseOver(true); + context_->SetCentralWidget(widget.release()); + } + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/StoneSampleCommands.yml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/StoneSampleCommands.yml Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,35 @@ +# +# 1 2 3 4 5 6 7 8 +# 345678901234567890123456789012345678901234567890123456789012345678901234567890 +# +rootName: StoneSampleCommands + +# +---------------------------------+ +# | Messages from TypeScript to C++ | +# +---------------------------------+ + +enum Tool: + - LineMeasure + - CircleMeasure + - Crop + - Windowing + - Zoom + - Pan + - Move + - Rotate + - Resize + - Mask + +struct SelectTool: + __handler: cpp + tool: Tool + +enum ActionType: + - UndoCrop + - Rotate + - Invert + +struct Action: + __handler: cpp + type: ActionType + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/StoneSampleCommands_generate.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/StoneSampleCommands_generate.py Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,16 @@ +import sys +import os + +# add the generation script location to the search paths +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'Resources', 'CodeGeneration')) + +# import the code generation tooling script +import stonegentool + +schemaFile = os.path.join(os.path.dirname(__file__), 'StoneSampleCommands.yml') +outDir = os.path.dirname(__file__) + +# ignition! +stonegentool.Process(schemaFile, outDir) + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/StoneSampleCommands_generated.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/StoneSampleCommands_generated.hpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,703 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 + +Generated on 2019-03-18 12:07:42.696093 by stonegentool + +*/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +//#define STONEGEN_NO_CPP11 1 + +#ifdef STONEGEN_NO_CPP11 +#define StoneSmartPtr std::unique_ptr +#else +#define StoneSmartPtr std::unique_ptr +#endif + +namespace StoneSampleCommands +{ + /** Throws in case of problem */ + inline void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asInt(); + } + + inline Json::Value _StoneSerializeValue(int32_t value) + { + Json::Value result(value); + return result; + } + + inline void _StoneDeserializeValue(Json::Value& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue; + } + + inline Json::Value _StoneSerializeValue(Json::Value value) + { + return value; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asDouble(); + } + + inline Json::Value _StoneSerializeValue(double value) + { + Json::Value result(value); + return result; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue) + { + destValue = jsonValue.asBool(); + } + + inline Json::Value _StoneSerializeValue(bool value) + { + Json::Value result(value); + return result; + } + + /** Throws in case of problem */ + inline void _StoneDeserializeValue( + std::string& destValue + , const Json::Value& jsonValue) + { + destValue = jsonValue.asString(); + } + + inline Json::Value _StoneSerializeValue(const std::string& value) + { + // the following is better than + Json::Value result(value.data(),value.data()+value.size()); + return result; + } + + inline std::string MakeIndent(size_t indent) + { + char* txt = reinterpret_cast(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!! + for(size_t i = 0; i < indent; ++i) + txt[i] = ' '; + txt[indent] = 0; + std::string retVal(txt); + free(txt); // NO EXCEPTION ABOVE !!!!!!!!!! + return retVal; + } + + // generic dumper + template + std::ostream& StoneDumpValue(std::ostream& out, const T& value, size_t indent) + { + out << MakeIndent(indent) << value; + return out; + } + + // string dumper + inline std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, size_t indent) + { + out << MakeIndent(indent) << "\"" << value << "\""; + return out; + } + + /** Throws in case of problem */ + template + void _StoneDeserializeValue( + std::map& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + for ( + Json::Value::const_iterator itr = jsonValue.begin(); + itr != jsonValue.end(); + itr++) + { + std::string key; + _StoneDeserializeValue(key, itr.key()); + + T innerDestValue; + _StoneDeserializeValue(innerDestValue, *itr); + + destValue[key] = innerDestValue; + } + } + + template + Json::Value _StoneSerializeValue(const std::map& value) + { + Json::Value result(Json::objectValue); + + for (typename std::map::const_iterator it = value.cbegin(); + it != value.cend(); ++it) + { + // it->first it->second + result[it->first] = _StoneSerializeValue(it->second); + } + return result; + } + + template + std::ostream& StoneDumpValue(std::ostream& out, const std::map& value, size_t indent) + { + out << MakeIndent(indent) << "{\n"; + for (typename std::map::const_iterator it = value.cbegin(); + it != value.cend(); ++it) + { + out << MakeIndent(indent+2) << "\"" << it->first << "\" : "; + StoneDumpValue(out, it->second, indent+2); + } + out << MakeIndent(indent) << "}\n"; + return out; + } + + /** Throws in case of problem */ + template + void _StoneDeserializeValue( + std::vector& destValue, const Json::Value& jsonValue) + { + destValue.clear(); + destValue.reserve(jsonValue.size()); + for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++) + { + T innerDestValue; + _StoneDeserializeValue(innerDestValue, jsonValue[i]); + destValue.push_back(innerDestValue); + } + } + + template + Json::Value _StoneSerializeValue(const std::vector& value) + { + Json::Value result(Json::arrayValue); + for (size_t i = 0; i < value.size(); ++i) + { + result.append(_StoneSerializeValue(value[i])); + } + return result; + } + + template + std::ostream& StoneDumpValue(std::ostream& out, const std::vector& value, size_t indent) + { + out << MakeIndent(indent) << "[\n"; + for (size_t i = 0; i < value.size(); ++i) + { + StoneDumpValue(out, value[i], indent+2); + } + out << MakeIndent(indent) << "]\n"; + return out; + } + + inline void StoneCheckSerializedValueTypeGeneric(const Json::Value& value) + { + if ((!value.isMember("type")) || (!value["type"].isString())) + { + std::stringstream ss; + ss << "Cannot deserialize value ('type' key invalid)"; + throw std::runtime_error(ss.str()); + } + } + + inline void StoneCheckSerializedValueType( + const Json::Value& value, std::string typeStr) + { + StoneCheckSerializedValueTypeGeneric(value); + + std::string actTypeStr = value["type"].asString(); + if (actTypeStr != typeStr) + { + std::stringstream ss; + ss << "Cannot deserialize type" << actTypeStr + << "into " << typeStr; + throw std::runtime_error(ss.str()); + } + } + + // end of generic methods + +// end of generic methods + + enum Tool { + Tool_LineMeasure, + Tool_CircleMeasure, + Tool_Crop, + Tool_Windowing, + Tool_Zoom, + Tool_Pan, + Tool_Move, + Tool_Rotate, + Tool_Resize, + Tool_Mask, + }; + + inline std::string ToString(const Tool& value) + { + if( value == Tool_LineMeasure) + { + return std::string("LineMeasure"); + } + if( value == Tool_CircleMeasure) + { + return std::string("CircleMeasure"); + } + if( value == Tool_Crop) + { + return std::string("Crop"); + } + if( value == Tool_Windowing) + { + return std::string("Windowing"); + } + if( value == Tool_Zoom) + { + return std::string("Zoom"); + } + if( value == Tool_Pan) + { + return std::string("Pan"); + } + if( value == Tool_Move) + { + return std::string("Move"); + } + if( value == Tool_Rotate) + { + return std::string("Rotate"); + } + if( value == Tool_Resize) + { + return std::string("Resize"); + } + if( value == Tool_Mask) + { + return std::string("Mask"); + } + std::stringstream ss; + ss << "Value \"" << value << "\" cannot be converted to Tool. Possible values are: " + << " LineMeasure = " << static_cast(Tool_LineMeasure) << ", " + << " CircleMeasure = " << static_cast(Tool_CircleMeasure) << ", " + << " Crop = " << static_cast(Tool_Crop) << ", " + << " Windowing = " << static_cast(Tool_Windowing) << ", " + << " Zoom = " << static_cast(Tool_Zoom) << ", " + << " Pan = " << static_cast(Tool_Pan) << ", " + << " Move = " << static_cast(Tool_Move) << ", " + << " Rotate = " << static_cast(Tool_Rotate) << ", " + << " Resize = " << static_cast(Tool_Resize) << ", " + << " Mask = " << static_cast(Tool_Mask) << ", " + << std::endl; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + inline void FromString(Tool& value, std::string strValue) + { + if( strValue == std::string("LineMeasure") ) + { + value = Tool_LineMeasure; + return; + } + if( strValue == std::string("CircleMeasure") ) + { + value = Tool_CircleMeasure; + return; + } + if( strValue == std::string("Crop") ) + { + value = Tool_Crop; + return; + } + if( strValue == std::string("Windowing") ) + { + value = Tool_Windowing; + return; + } + if( strValue == std::string("Zoom") ) + { + value = Tool_Zoom; + return; + } + if( strValue == std::string("Pan") ) + { + value = Tool_Pan; + return; + } + if( strValue == std::string("Move") ) + { + value = Tool_Move; + return; + } + if( strValue == std::string("Rotate") ) + { + value = Tool_Rotate; + return; + } + if( strValue == std::string("Resize") ) + { + value = Tool_Resize; + return; + } + if( strValue == std::string("Mask") ) + { + value = Tool_Mask; + return; + } + + std::stringstream ss; + ss << "String \"" << strValue << "\" cannot be converted to Tool. Possible values are: LineMeasure CircleMeasure Crop Windowing Zoom Pan Move Rotate Resize Mask "; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + + inline void _StoneDeserializeValue( + Tool& destValue, const Json::Value& jsonValue) + { + FromString(destValue, jsonValue.asString()); + } + + inline Json::Value _StoneSerializeValue(const Tool& value) + { + std::string strValue = ToString(value); + return Json::Value(strValue); + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const Tool& value, size_t indent = 0) + { + if( value == Tool_LineMeasure) + { + out << MakeIndent(indent) << "LineMeasure" << std::endl; + } + if( value == Tool_CircleMeasure) + { + out << MakeIndent(indent) << "CircleMeasure" << std::endl; + } + if( value == Tool_Crop) + { + out << MakeIndent(indent) << "Crop" << std::endl; + } + if( value == Tool_Windowing) + { + out << MakeIndent(indent) << "Windowing" << std::endl; + } + if( value == Tool_Zoom) + { + out << MakeIndent(indent) << "Zoom" << std::endl; + } + if( value == Tool_Pan) + { + out << MakeIndent(indent) << "Pan" << std::endl; + } + if( value == Tool_Move) + { + out << MakeIndent(indent) << "Move" << std::endl; + } + if( value == Tool_Rotate) + { + out << MakeIndent(indent) << "Rotate" << std::endl; + } + if( value == Tool_Resize) + { + out << MakeIndent(indent) << "Resize" << std::endl; + } + if( value == Tool_Mask) + { + out << MakeIndent(indent) << "Mask" << std::endl; + } + return out; + } + + + enum ActionType { + ActionType_UndoCrop, + ActionType_Rotate, + ActionType_Invert, + }; + + inline std::string ToString(const ActionType& value) + { + if( value == ActionType_UndoCrop) + { + return std::string("UndoCrop"); + } + if( value == ActionType_Rotate) + { + return std::string("Rotate"); + } + if( value == ActionType_Invert) + { + return std::string("Invert"); + } + std::stringstream ss; + ss << "Value \"" << value << "\" cannot be converted to ActionType. Possible values are: " + << " UndoCrop = " << static_cast(ActionType_UndoCrop) << ", " + << " Rotate = " << static_cast(ActionType_Rotate) << ", " + << " Invert = " << static_cast(ActionType_Invert) << ", " + << std::endl; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + inline void FromString(ActionType& value, std::string strValue) + { + if( strValue == std::string("UndoCrop") ) + { + value = ActionType_UndoCrop; + return; + } + if( strValue == std::string("Rotate") ) + { + value = ActionType_Rotate; + return; + } + if( strValue == std::string("Invert") ) + { + value = ActionType_Invert; + return; + } + + std::stringstream ss; + ss << "String \"" << strValue << "\" cannot be converted to ActionType. Possible values are: UndoCrop Rotate Invert "; + std::string msg = ss.str(); + throw std::runtime_error(msg); + } + + + inline void _StoneDeserializeValue( + ActionType& destValue, const Json::Value& jsonValue) + { + FromString(destValue, jsonValue.asString()); + } + + inline Json::Value _StoneSerializeValue(const ActionType& value) + { + std::string strValue = ToString(value); + return Json::Value(strValue); + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const ActionType& value, size_t indent = 0) + { + if( value == ActionType_UndoCrop) + { + out << MakeIndent(indent) << "UndoCrop" << std::endl; + } + if( value == ActionType_Rotate) + { + out << MakeIndent(indent) << "Rotate" << std::endl; + } + if( value == ActionType_Invert) + { + out << MakeIndent(indent) << "Invert" << std::endl; + } + return out; + } + + + +#ifdef _MSC_VER +#pragma region SelectTool +#endif //_MSC_VER + + struct SelectTool + { + Tool tool; + + SelectTool(Tool tool = Tool()) + { + this->tool = tool; + } + }; + + inline void _StoneDeserializeValue(SelectTool& destValue, const Json::Value& value) + { + _StoneDeserializeValue(destValue.tool, value["tool"]); + } + + inline Json::Value _StoneSerializeValue(const SelectTool& value) + { + Json::Value result(Json::objectValue); + result["tool"] = _StoneSerializeValue(value.tool); + + return result; + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const SelectTool& value, size_t indent = 0) + { + out << MakeIndent(indent) << "{\n"; + out << MakeIndent(indent) << "tool:\n"; + StoneDumpValue(out, value.tool,indent+2); + out << "\n"; + + out << MakeIndent(indent) << "}\n"; + return out; + } + + inline void StoneDeserialize(SelectTool& destValue, const Json::Value& value) + { + StoneCheckSerializedValueType(value, "StoneSampleCommands.SelectTool"); + _StoneDeserializeValue(destValue, value["value"]); + } + + inline Json::Value StoneSerializeToJson(const SelectTool& value) + { + Json::Value result(Json::objectValue); + result["type"] = "StoneSampleCommands.SelectTool"; + result["value"] = _StoneSerializeValue(value); + return result; + } + + inline std::string StoneSerialize(const SelectTool& value) + { + Json::Value resultJson = StoneSerializeToJson(value); + std::string resultStr = resultJson.toStyledString(); + return resultStr; + } + +#ifdef _MSC_VER +#pragma endregion SelectTool +#endif //_MSC_VER + +#ifdef _MSC_VER +#pragma region Action +#endif //_MSC_VER + + struct Action + { + ActionType type; + + Action(ActionType type = ActionType()) + { + this->type = type; + } + }; + + inline void _StoneDeserializeValue(Action& destValue, const Json::Value& value) + { + _StoneDeserializeValue(destValue.type, value["type"]); + } + + inline Json::Value _StoneSerializeValue(const Action& value) + { + Json::Value result(Json::objectValue); + result["type"] = _StoneSerializeValue(value.type); + + return result; + } + + inline std::ostream& StoneDumpValue(std::ostream& out, const Action& value, size_t indent = 0) + { + out << MakeIndent(indent) << "{\n"; + out << MakeIndent(indent) << "type:\n"; + StoneDumpValue(out, value.type,indent+2); + out << "\n"; + + out << MakeIndent(indent) << "}\n"; + return out; + } + + inline void StoneDeserialize(Action& destValue, const Json::Value& value) + { + StoneCheckSerializedValueType(value, "StoneSampleCommands.Action"); + _StoneDeserializeValue(destValue, value["value"]); + } + + inline Json::Value StoneSerializeToJson(const Action& value) + { + Json::Value result(Json::objectValue); + result["type"] = "StoneSampleCommands.Action"; + result["value"] = _StoneSerializeValue(value); + return result; + } + + inline std::string StoneSerialize(const Action& value) + { + Json::Value resultJson = StoneSerializeToJson(value); + std::string resultStr = resultJson.toStyledString(); + return resultStr; + } + +#ifdef _MSC_VER +#pragma endregion Action +#endif //_MSC_VER + +#ifdef _MSC_VER +#pragma region Dispatching code +#endif //_MSC_VER + + class IHandler + { + public: + virtual bool Handle(const SelectTool& value) = 0; + virtual bool Handle(const Action& value) = 0; + }; + + /** Service function for StoneDispatchToHandler */ + inline bool StoneDispatchJsonToHandler( + const Json::Value& jsonValue, IHandler* handler) + { + StoneCheckSerializedValueTypeGeneric(jsonValue); + std::string type = jsonValue["type"].asString(); + if (type == "") + { + // this should never ever happen + throw std::runtime_error("Caught empty type while dispatching"); + } + else if (type == "StoneSampleCommands.SelectTool") + { + SelectTool value; + _StoneDeserializeValue(value, jsonValue["value"]); + return handler->Handle(value); + } + else if (type == "StoneSampleCommands.Action") + { + Action value; + _StoneDeserializeValue(value, jsonValue["value"]); + return handler->Handle(value); + } + else + { + return false; + } + } + + /** Takes a serialized type and passes this to the handler */ + inline bool StoneDispatchToHandler(std::string strValue, IHandler* handler) + { + Json::Value readValue; + + Json::CharReaderBuilder builder; + Json::CharReader* reader = builder.newCharReader(); + + StoneSmartPtr ptr(reader); + + std::string errors; + + bool ok = reader->parse( + strValue.c_str(), + strValue.c_str() + strValue.size(), + &readValue, + &errors + ); + if (!ok) + { + std::stringstream ss; + ss << "Jsoncpp parsing error: " << errors; + throw std::runtime_error(ss.str()); + } + return StoneDispatchJsonToHandler(readValue, handler); + } + +#ifdef _MSC_VER +#pragma endregion Dispatching code +#endif //_MSC_VER +} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/StoneSampleCommands_generated.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/StoneSampleCommands_generated.ts Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,333 @@ +/* + 1 2 3 4 5 6 7 +12345678901234567890123456789012345678901234567890123456789012345678901234567890 + +Generated on 2019-03-18 12:07:42.696093 by stonegentool + +*/ + +function StoneCheckSerializedValueType(value: any, typeStr: string) +{ + StoneCheckSerializedValueTypeGeneric(value); + + if (value['type'] != typeStr) + { + throw new Error( + `Cannot deserialize type ${value['type']} into ${typeStr}`); + } +} + +function isString(val: any) :boolean +{ + return ((typeof val === 'string') || (val instanceof String)); +} + +function StoneCheckSerializedValueTypeGeneric(value: any) +{ + // console.//log("+-------------------------------------------------+"); + // console.//log("| StoneCheckSerializedValueTypeGeneric |"); + // console.//log("+-------------------------------------------------+"); + // console.//log("value = "); + // console.//log(value); + if ( (!('type' in value)) || (!isString(value.type)) ) + { + throw new Error( + "Cannot deserialize value ('type' key invalid)"); + } +} + +// end of generic methods + +export enum Tool { + LineMeasure = "LineMeasure", + CircleMeasure = "CircleMeasure", + Crop = "Crop", + Windowing = "Windowing", + Zoom = "Zoom", + Pan = "Pan", + Move = "Move", + Rotate = "Rotate", + Resize = "Resize", + Mask = "Mask" +}; + +export function Tool_FromString(strValue:string) : Tool +{ + if( strValue == "LineMeasure" ) + { + return Tool.LineMeasure; + } + if( strValue == "CircleMeasure" ) + { + return Tool.CircleMeasure; + } + if( strValue == "Crop" ) + { + return Tool.Crop; + } + if( strValue == "Windowing" ) + { + return Tool.Windowing; + } + if( strValue == "Zoom" ) + { + return Tool.Zoom; + } + if( strValue == "Pan" ) + { + return Tool.Pan; + } + if( strValue == "Move" ) + { + return Tool.Move; + } + if( strValue == "Rotate" ) + { + return Tool.Rotate; + } + if( strValue == "Resize" ) + { + return Tool.Resize; + } + if( strValue == "Mask" ) + { + return Tool.Mask; + } + + let msg : string = `String ${strValue} cannot be converted to Tool. Possible values are: LineMeasure, CircleMeasure, Crop, Windowing, Zoom, Pan, Move, Rotate, Resize, Mask`; + throw new Error(msg); +} + +export function Tool_ToString(value:Tool) : string +{ + if( value == Tool.LineMeasure ) + { + return "LineMeasure"; + } + if( value == Tool.CircleMeasure ) + { + return "CircleMeasure"; + } + if( value == Tool.Crop ) + { + return "Crop"; + } + if( value == Tool.Windowing ) + { + return "Windowing"; + } + if( value == Tool.Zoom ) + { + return "Zoom"; + } + if( value == Tool.Pan ) + { + return "Pan"; + } + if( value == Tool.Move ) + { + return "Move"; + } + if( value == Tool.Rotate ) + { + return "Rotate"; + } + if( value == Tool.Resize ) + { + return "Resize"; + } + if( value == Tool.Mask ) + { + return "Mask"; + } + + let msg : string = `Value ${value} cannot be converted to Tool. Possible values are: `; + { + let _LineMeasure_enumValue : string = Tool.LineMeasure; // enums are strings in stonecodegen, so this will work. + let msg_LineMeasure : string = `LineMeasure (${_LineMeasure_enumValue}), `; + msg = msg + msg_LineMeasure; + } + { + let _CircleMeasure_enumValue : string = Tool.CircleMeasure; // enums are strings in stonecodegen, so this will work. + let msg_CircleMeasure : string = `CircleMeasure (${_CircleMeasure_enumValue}), `; + msg = msg + msg_CircleMeasure; + } + { + let _Crop_enumValue : string = Tool.Crop; // enums are strings in stonecodegen, so this will work. + let msg_Crop : string = `Crop (${_Crop_enumValue}), `; + msg = msg + msg_Crop; + } + { + let _Windowing_enumValue : string = Tool.Windowing; // enums are strings in stonecodegen, so this will work. + let msg_Windowing : string = `Windowing (${_Windowing_enumValue}), `; + msg = msg + msg_Windowing; + } + { + let _Zoom_enumValue : string = Tool.Zoom; // enums are strings in stonecodegen, so this will work. + let msg_Zoom : string = `Zoom (${_Zoom_enumValue}), `; + msg = msg + msg_Zoom; + } + { + let _Pan_enumValue : string = Tool.Pan; // enums are strings in stonecodegen, so this will work. + let msg_Pan : string = `Pan (${_Pan_enumValue}), `; + msg = msg + msg_Pan; + } + { + let _Move_enumValue : string = Tool.Move; // enums are strings in stonecodegen, so this will work. + let msg_Move : string = `Move (${_Move_enumValue}), `; + msg = msg + msg_Move; + } + { + let _Rotate_enumValue : string = Tool.Rotate; // enums are strings in stonecodegen, so this will work. + let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `; + msg = msg + msg_Rotate; + } + { + let _Resize_enumValue : string = Tool.Resize; // enums are strings in stonecodegen, so this will work. + let msg_Resize : string = `Resize (${_Resize_enumValue}), `; + msg = msg + msg_Resize; + } + { + let _Mask_enumValue : string = Tool.Mask; // enums are strings in stonecodegen, so this will work. + let msg_Mask : string = `Mask (${_Mask_enumValue})`; + msg = msg + msg_Mask; + } + throw new Error(msg); +} + +export enum ActionType { + UndoCrop = "UndoCrop", + Rotate = "Rotate", + Invert = "Invert" +}; + +export function ActionType_FromString(strValue:string) : ActionType +{ + if( strValue == "UndoCrop" ) + { + return ActionType.UndoCrop; + } + if( strValue == "Rotate" ) + { + return ActionType.Rotate; + } + if( strValue == "Invert" ) + { + return ActionType.Invert; + } + + let msg : string = `String ${strValue} cannot be converted to ActionType. Possible values are: UndoCrop, Rotate, Invert`; + throw new Error(msg); +} + +export function ActionType_ToString(value:ActionType) : string +{ + if( value == ActionType.UndoCrop ) + { + return "UndoCrop"; + } + if( value == ActionType.Rotate ) + { + return "Rotate"; + } + if( value == ActionType.Invert ) + { + return "Invert"; + } + + let msg : string = `Value ${value} cannot be converted to ActionType. Possible values are: `; + { + let _UndoCrop_enumValue : string = ActionType.UndoCrop; // enums are strings in stonecodegen, so this will work. + let msg_UndoCrop : string = `UndoCrop (${_UndoCrop_enumValue}), `; + msg = msg + msg_UndoCrop; + } + { + let _Rotate_enumValue : string = ActionType.Rotate; // enums are strings in stonecodegen, so this will work. + let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `; + msg = msg + msg_Rotate; + } + { + let _Invert_enumValue : string = ActionType.Invert; // enums are strings in stonecodegen, so this will work. + let msg_Invert : string = `Invert (${_Invert_enumValue})`; + msg = msg + msg_Invert; + } + throw new Error(msg); +} + + + +export class SelectTool { + tool:Tool; + + constructor() { + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'StoneSampleCommands.SelectTool'; + container['value'] = this; + return JSON.stringify(container); + } + + public static StoneDeserialize(valueStr: string) : SelectTool + { + let value: any = JSON.parse(valueStr); + StoneCheckSerializedValueType(value, 'StoneSampleCommands.SelectTool'); + let result: SelectTool = value['value'] as SelectTool; + return result; + } +} +export class Action { + type:ActionType; + + constructor() { + } + + public StoneSerialize(): string { + let container: object = {}; + container['type'] = 'StoneSampleCommands.Action'; + container['value'] = this; + return JSON.stringify(container); + } + + public static StoneDeserialize(valueStr: string) : Action + { + let value: any = JSON.parse(valueStr); + StoneCheckSerializedValueType(value, 'StoneSampleCommands.Action'); + let result: Action = value['value'] as Action; + return result; + } +} + +export interface IHandler { +}; + +/** Service function for StoneDispatchToHandler */ +export function StoneDispatchJsonToHandler( + jsonValue: any, handler: IHandler): boolean +{ + StoneCheckSerializedValueTypeGeneric(jsonValue); + let type: string = jsonValue["type"]; + if (type == "") + { + // this should never ever happen + throw new Error("Caught empty type while dispatching"); + } + else + { + return false; + } +} + +/** Takes a serialized type and passes this to the handler */ +export function StoneDispatchToHandler( + strValue: string, handler: IHandler): boolean +{ + // console.//log("+------------------------------------------------+"); + // console.//log("| StoneDispatchToHandler |"); + // console.//log("+------------------------------------------------+"); + // console.//log("strValue = "); + // console.//log(strValue); + let jsonValue: any = JSON.parse(strValue) + return StoneDispatchJsonToHandler(jsonValue, handler); +} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/SynchronizedSeriesApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/SynchronizedSeriesApplication.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,109 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleInteractor.h" + +#include "../../../Framework/Toolbox/OrthancSeriesLoader.h" +#include "../../../Framework/Layers/SeriesFrameRendererFactory.h" +#include "../../../Framework/Layers/ReferenceLineFactory.h" +#include "../../../Framework/Widgets/LayoutWidget.h" + +#include + +namespace OrthancStone +{ + namespace Samples + { + class SynchronizedSeriesApplication : public SampleApplicationBase + { + private: + LayeredSceneWidget* CreateSeriesWidget(BasicApplicationContext& context, + const std::string& series) + { + std::unique_ptr loader + (new OrthancSeriesLoader(context.GetWebService().GetConnection(), series)); + + std::unique_ptr interactor(new SampleInteractor(*loader, false)); + + std::unique_ptr widget(new LayeredSceneWidget); + widget->AddLayer(new SeriesFrameRendererFactory(loader.release(), false)); + widget->SetSlice(interactor->GetCursor().GetCurrentSlice()); + widget->SetInteractor(*interactor); + + context.AddInteractor(interactor.release()); + + return widget.release(); + } + + public: + virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("a", boost::program_options::value(), + "Orthanc ID of the 1st series") + ("b", boost::program_options::value(), + "Orthanc ID of the 2nd series") + ("c", boost::program_options::value(), + "Orthanc ID of the 3rd series") + ; + + options.add(generic); + } + + virtual void Initialize(BasicApplicationContext& context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + if (parameters.count("a") != 1 || + parameters.count("b") != 1 || + parameters.count("c") != 1) + { + LOG(ERROR) << "At least one of the three series IDs is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::unique_ptr a(CreateSeriesWidget(context, parameters["a"].as())); + std::unique_ptr b(CreateSeriesWidget(context, parameters["b"].as())); + std::unique_ptr c(CreateSeriesWidget(context, parameters["c"].as())); + + ReferenceLineFactory::Configure(*a, *b); + ReferenceLineFactory::Configure(*a, *c); + ReferenceLineFactory::Configure(*b, *c); + + std::unique_ptr layout(new LayoutWidget); + layout->SetPadding(5); + layout->AddWidget(a.release()); + + std::unique_ptr layoutB(new LayoutWidget); + layoutB->SetVertical(); + layoutB->SetPadding(5); + layoutB->AddWidget(b.release()); + layoutB->AddWidget(c.release()); + layout->AddWidget(layoutB.release()); + + context.SetCentralWidget(layout.release()); + } + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/TestPatternApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/TestPatternApplication.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleApplicationBase.h" + +#include "../../../Framework/Widgets/TestCairoWidget.h" +#include "../../../Framework/Widgets/TestWorldSceneWidget.h" +#include "../../../Framework/Widgets/LayoutWidget.h" + +namespace OrthancStone +{ + namespace Samples + { + class TestPatternApplication : public SampleApplicationBase + { + public: + virtual void DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("animate", boost::program_options::value()->default_value(true), "Animate the test pattern") + ; + + options.add(generic); + } + + virtual void Initialize(IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + std::unique_ptr layout(new LayoutWidget); + layout->SetPadding(10); + layout->SetBackgroundCleared(true); + layout->AddWidget(new TestCairoWidget(parameters["animate"].as())); + layout->AddWidget(new TestWorldSceneWidget(parameters["animate"].as())); + + context_->SetCentralWidget(layout.release()); + context_->SetUpdateDelay(25); // If animation, update the content each 25ms + } + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/index.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,23 @@ + + + + + + + + + + + + Wasm Samples + + + + + + \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/samples-styles.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/samples-styles.css Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,16 @@ +html, body { + width: 100%; + height: 100%; + margin: 0px; + border: 0; + overflow: hidden; /* Disable scrollbars */ + display: block; /* No floating content on sides */ + background-color: black; + color: white; + font-family: Arial, Helvetica, sans-serif; +} + +canvas { + left:0px; + top:0px; +} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/simple-viewer-single-file.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/simple-viewer-single-file.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,39 @@ + + + + + + + + + + + + + Simple Viewer + + + + +
+
+ +
+
+ +
+
+
+ line + circle + + +
+ + + + \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/simple-viewer-single-file.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/simple-viewer-single-file.ts Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,61 @@ +import wasmApplicationRunner = require('../../../Platforms/Wasm/wasm-application-runner'); + +wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSimpleViewerSingleFile", "/orthanc"); + +function SelectTool(toolName: string) { + var command = { + command: "selectTool", + args: { + toolName: toolName + } + }; + wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); + +} + +function PerformAction(commandName: string) { + var command = { + command: commandName, + commandType: "simple", + args: {} + }; + wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); +} + +//initializes the buttons +//----------------------- +// install "SelectTool" handlers +document.querySelectorAll("[tool-selector]").forEach((e) => { + console.log(e); + (e as HTMLInputElement).addEventListener("click", () => { + console.log(e); + SelectTool(e.attributes["tool-selector"].value); + }); +}); + +// install "PerformAction" handlers +document.querySelectorAll("[action-trigger]").forEach((e) => { + (e as HTMLInputElement).addEventListener("click", () => { + PerformAction(e.attributes["action-trigger"].value); + }); +}); + +// this method is called "from the C++ code" when the StoneApplication is updated. +// it can be used to update the UI of the application +function UpdateWebApplicationWithString(statusUpdateMessage: string) { + console.log(statusUpdateMessage); + + if (statusUpdateMessage.startsWith("series-description=")) { + document.getElementById("series-description").innerText = statusUpdateMessage.split("=")[1]; + } +} + +function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) { + console.log("updating web application with serialized message: ", statusUpdateMessageString); + console.log(""); +} + +// make it available to other js scripts in the application +( window).UpdateWebApplicationWithString = UpdateWebApplicationWithString; + +( window).UpdateWebApplicationWithSerializedMessage = UpdateWebApplicationWithSerializedMessage; diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/simple-viewer-single-file.tsconfig.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/simple-viewer-single-file.tsconfig.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,9 @@ +{ + "extends" : "./tsconfig-samples", + "compilerOptions": { + // "outFile": "../build-web/app-simple-viewer-single-file.js" + }, + "include" : [ + "simple-viewer-single-file.ts" + ] +} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/single-frame-editor.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/single-frame-editor.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,22 @@ + + + + + + + + + + + + Simple Viewer + + + +
+ +
+ + + + \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/single-frame-editor.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/single-frame-editor.ts Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,3 @@ +import wasmApplicationRunner = require('../../../Platforms/Wasm/wasm-application-runner'); + +wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSingleFrameEditor", "/orthanc"); diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/single-frame-editor.tsconfig.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/single-frame-editor.tsconfig.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,8 @@ +{ + "extends" : "./tsconfig-samples", + "compilerOptions": { + }, + "include" : [ + "single-frame-editor.ts" + ] +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/single-frame.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/single-frame.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,22 @@ + + + + + + + + + + + + Simple Viewer + + + +
+ +
+ + + + \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/single-frame.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/single-frame.ts Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,4 @@ +import wasmApplicationRunner = require('../../../Platforms/Wasm/wasm-application-runner'); + +wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSingleFrame", "/orthanc"); + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/single-frame.tsconfig.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/single-frame.tsconfig.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,8 @@ +{ + "extends" : "./tsconfig-samples", + "compilerOptions": { + }, + "include" : [ + "single-frame.ts" + ] +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/Web/tsconfig-samples.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/Web/tsconfig-samples.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,11 @@ +{ + "extends" : "../../../Platforms/Wasm/tsconfig-stone", + "compilerOptions": { + "sourceMap": false, + "lib" : [ + "es2017", + "dom", + "dom.iterable" + ] + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/build-wasm.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/build-wasm.sh Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,27 @@ +#!/bin/bash +# +# usage: +# to build all targets in Debug: +# ./build-wasm.sh +# +# to build a single target in release: +# ./build-wasm.sh OrthancStoneSingleFrameEditor Release + +set -e + +target=${1:-all} +buildType=${2:-Debug} + +currentDir=$(pwd) +samplesRootDir=$(pwd) + +mkdir -p $samplesRootDir/build-wasm +cd $samplesRootDir/build-wasm + +source ~/apps/emsdk/emsdk_env.sh +cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=~/apps/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=$buildType -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc -DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON +ninja $target + +echo "-- building the web application -- " +cd $currentDir +./build-web.sh \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/build-wasm.sh.old --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/build-wasm.sh.old Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,33 @@ +#!/bin/bash +# +# usage: +# to build all targets: +# ./build-wasm.sh +# +# to build a single target: +# ./build-wasm.sh OrthancStoneSingleFrameEditor + +set -e + +target=${1:-all} + +currentDir=$(pwd) +samplesRootDir=$(pwd) + +mkdir -p $samplesRootDir/build-wasm +cd $samplesRootDir/build-wasm + +source ~/apps/emsdk/emsdk_env.sh +cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone \ + -DORTHANC_FRAMEWORK_SOURCE=path \ + -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc \ + -DALLOW_DOWNLOADS=ON .. \ + -DENABLE_WASM=ON + +ninja $target + +echo "-- building the web application -- " +cd $currentDir +./build-web.sh diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/build-web-ext.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/build-web-ext.sh Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,58 @@ +#!/bin/bash + +set -e + +target=${1:-all} +# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/ + +currentDir=$(pwd) + +scriptDirRel=$(dirname $0) +#echo $scriptDirRel +scriptDirAbs=$(realpath $scriptDirRel) +echo $scriptDirAbs + +samplesRootDir=scriptDirAbs + +outputDir=$samplesRootDir/build-web/ +mkdir -p $outputDir + +# files used by all single files samples +cp $samplesRootDir/Web/index.html $outputDir +cp $samplesRootDir/Web/samples-styles.css $outputDir + +# build simple-viewer-single-file (obsolete project) +if [[ $target == "all" || $target == "OrthancStoneSimpleViewerSingleFile" ]]; then + cp $samplesRootDir/Web/simple-viewer-single-file.html $outputDir + tsc --allowJs --project $samplesRootDir/Web/simple-viewer-single-file.tsconfig.json + cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.js $outputDir + cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.wasm $outputDir +fi + +# build single-frame +if [[ $target == "all" || $target == "OrthancStoneSingleFrame" ]]; then + cp $samplesRootDir/Web/single-frame.html $outputDir + tsc --allowJs --project $samplesRootDir/Web/single-frame.tsconfig.json + cp $currentDir/build-wasm/OrthancStoneSingleFrame.js $outputDir + cp $currentDir/build-wasm/OrthancStoneSingleFrame.wasm $outputDir +fi + +# build single-frame-editor +if [[ $target == "all" || $target == "OrthancStoneSingleFrameEditor" ]]; then + cp $samplesRootDir/Web/single-frame-editor.html $outputDir + tsc --allowJs --project $samplesRootDir/Web/single-frame-editor.tsconfig.json + cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.js $outputDir + cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.wasm $outputDir +fi + +# build simple-viewer project +if [[ $target == "all" || $target == "OrthancStoneSimpleViewer" ]]; then + mkdir -p $outputDir/simple-viewer/ + cp $samplesRootDir/SimpleViewer/Wasm/simple-viewer.html $outputDir/simple-viewer/ + cp $samplesRootDir/SimpleViewer/Wasm/styles.css $outputDir/simple-viewer/ + tsc --allowJs --project $samplesRootDir/SimpleViewer/Wasm/tsconfig-simple-viewer.json + cp $currentDir/build-wasm/OrthancStoneSimpleViewer.js $outputDir/simple-viewer/ + cp $currentDir/build-wasm/OrthancStoneSimpleViewer.wasm $outputDir/simple-viewer/ +fi + +cd $currentDir diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/build-web.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/build-web.sh Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,74 @@ +#!/bin/bash + +set -e + +target=${1:-all} +# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/ + +currentDir=$(pwd) +samplesRootDir=$(pwd) + +echo "*************************************************************************" +echo "samplesRootDir = $samplesRootDir" +echo "*************************************************************************" + +outputDir=$samplesRootDir/build-web/ +mkdir -p "$outputDir" + +# files used by all single files samples +cp "$samplesRootDir/Web/index.html" "$outputDir" +cp "$samplesRootDir/Web/samples-styles.css" "$outputDir" + +# # build simple-viewer-single-file (obsolete project) +# if [[ $target == "all" || $target == "OrthancStoneSimpleViewerSingleFile" ]]; then +# cp $samplesRootDir/Web/simple-viewer-single-file.html $outputDir +# tsc --project $samplesRootDir/Web/simple-viewer-single-file.tsconfig.json --outDir "$outputDir" +# browserify \ +# "$outputDir/Platforms/Wasm/wasm-application-runner.js" \ +# "$outputDir/Applications/Samples/Web/simple-viewer-single-file.js" \ +# -o "$outputDir/app-simple-viewer-single-file.js" +# cp "$currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.js" $outputDir +# cp "$currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.wasm" $outputDir +# fi + +# # build single-frame +# if [[ $target == "all" || $target == "OrthancStoneSingleFrame" ]]; then +# cp $samplesRootDir/Web/single-frame.html $outputDir +# tsc --project $samplesRootDir/Web/single-frame.tsconfig.json --outDir "$outputDir" +# browserify \ +# "$outputDir/Platforms/Wasm/wasm-application-runner.js" \ +# "$outputDir/Applications/Samples/Web/single-frame.js" \ +# -o "$outputDir/app-single-frame.js" +# cp "$currentDir/build-wasm/OrthancStoneSingleFrame.js" $outputDir +# cp "$currentDir/build-wasm/OrthancStoneSingleFrame.wasm" $outputDir +# fi + +# build single-frame-editor +if [[ $target == "all" || $target == "OrthancStoneSingleFrameEditor" ]]; then + cp $samplesRootDir/Web/single-frame-editor.html $outputDir + tsc --project $samplesRootDir/Web/single-frame-editor.tsconfig.json --outDir "$outputDir" + browserify \ + "$outputDir/Platforms/Wasm/wasm-application-runner.js" \ + "$outputDir/Applications/Samples/Web/single-frame-editor.js" \ + -o "$outputDir/app-single-frame-editor.js" + cp "$currentDir/build-wasm/OrthancStoneSingleFrameEditor.js" $outputDir + cp "$currentDir/build-wasm/OrthancStoneSingleFrameEditor.wasm" $outputDir +fi + +# build simple-viewer project +if [[ $target == "all" || $target == "OrthancStoneSimpleViewer" ]]; then + mkdir -p $outputDir/simple-viewer/ + cp $samplesRootDir/SimpleViewer/Wasm/simple-viewer.html $outputDir/simple-viewer/ + cp $samplesRootDir/SimpleViewer/Wasm/styles.css $outputDir/simple-viewer/ + + # the root dir must contain all the source files for the whole project + tsc --module commonjs --allowJs --project "$samplesRootDir/SimpleViewer/Wasm/tsconfig-simple-viewer.json" --rootDir "$samplesRootDir/../.." --outDir "$outputDir/simple-viewer/" + browserify \ + "$outputDir/simple-viewer/Platforms/Wasm/wasm-application-runner.js" \ + "$outputDir/simple-viewer/Applications/Samples/SimpleViewer/Wasm/simple-viewer.js" \ + -o "$outputDir/simple-viewer/app-simple-viewer.js" + cp "$currentDir/build-wasm/OrthancStoneSimpleViewer.js" "$outputDir/simple-viewer/" + cp "$currentDir/build-wasm/OrthancStoneSimpleViewer.wasm" "$outputDir/simple-viewer/" +fi + +cd $currentDir diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/get-requirements-windows.ps1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/get-requirements-windows.ps1 Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,50 @@ + +if ($true) { + + Write-Error "This script is obsolete. Please work under WSL and run build-wasm.sh" + +} else { + + param( + [IO.DirectoryInfo] $EmsdkRootDir = "C:\Emscripten", + [bool] $Overwrite = $false + ) + + if (Test-Path -Path $EmsdkRootDir) { + if( $Override) { + Remove-Item -Path $EmsdkRootDir -Force -Recurse + } else { + throw "The `"$EmsdkRootDir`" folder may not exist! Use the Overwrite flag to bypass this check." + } + } + + # TODO: detect whether git is installed + # choco install -y git + + Write-Host "Will retrieve the Emscripten SDK to the `"$EmsdkRootDir`" folder" + + $EmsdkParentDir = split-path -Parent $EmsdkRootDir + $EmsdkRootName = split-path -Leaf $EmsdkRootDir + + Push-Location $EmsdkParentDir + + git clone https://github.com/juj/emsdk.git $EmsdkRootName + cd $EmsdkRootName + + git pull + + ./emsdk install latest + + ./emsdk activate latest + + echo "INFO: the ~/.emscripten file has been configured for this installation of Emscripten." + + Write-Host "emsdk is now installed in $EmsdkRootDir" + + Pop-Location + +} + + + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/nginx.local.conf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/nginx.local.conf Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,44 @@ +# Local config to serve the WASM samples static files and reverse proxy Orthanc. +# Uses port 9977 instead of 80. + +# `events` section is mandatory +events { + worker_connections 1024; # Default: 1024 +} + +http { + + # prevent nginx sync issues on OSX + proxy_buffering off; + + server { + listen 9977 default_server; + client_max_body_size 4G; + + # location may have to be adjusted depending on your OS and nginx install + include /etc/nginx/mime.types; + # if not in your system mime.types, add this line to support WASM: + # types { + # application/wasm wasm; + # } + + # serve WASM static files + root build-web/; + location / { + } + + # reverse proxy orthanc + location /orthanc/ { + rewrite /orthanc(.*) $1 break; + proxy_pass http://127.0.0.1:8042; + proxy_set_header Host $http_host; + proxy_set_header my-auth-header good-token; + proxy_request_buffering off; + proxy_max_temp_file_size 0; + client_max_body_size 0; + } + + + } + +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/package-lock.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/package-lock.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,11 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "typescript": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", + "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==" + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/CMakeLists.txt Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,142 @@ +cmake_minimum_required(VERSION 2.8.3) +project(RtViewerDemo) + +if(MSVC) + add_definitions(/MP) + if (CMAKE_BUILD_TYPE MATCHES DEBUG) + add_definitions(/JMC) + endif() +endif() + +message("-------------------------------------------------------------------------------------------------------------------") +message("ORTHANC_FRAMEWORK_ROOT is set to ${ORTHANC_FRAMEWORK_ROOT}") +message("-------------------------------------------------------------------------------------------------------------------") + +if(NOT DEFINED ORTHANC_FRAMEWORK_ROOT) + message(FATAL_ERROR "The location of the Orthanc source repository must be set in the ORTHANC_FRAMEWORK_ROOT CMake variable") +endif() + +message("-------------------------------------------------------------------------------------------------------------------") +message("STONE_SOURCES_DIR is set to ${STONE_SOURCES_DIR}") +message("-------------------------------------------------------------------------------------------------------------------") + +if(NOT DEFINED STONE_SOURCES_DIR) + message(FATAL_ERROR "The location of the Stone of Orthanc source repository must be set in the STONE_SOURCES_DIR CMake variable") +endif() + +include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneParameters.cmake) + +if (OPENSSL_NO_CAPIENG) +add_definitions(-DOPENSSL_NO_CAPIENG=1) +endif() + +set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") +set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") +set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") + +if (ENABLE_WASM) + ##################################################################### + ## Configuration of the Emscripten compiler for WebAssembly target + ##################################################################### + + set(WASM_FLAGS "-s WASM=1") + set(WASM_FLAGS "${WASM_FLAGS} -s STRICT=1") # drops support for all deprecated build options + set(WASM_FLAGS "${WASM_FLAGS} -s FILESYSTEM=1") # if we don't include it, gen_uuid.c fails to build because srand, getpid(), ... are not defined + set(WASM_FLAGS "${WASM_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0") # actually enable exception catching + set(WASM_FLAGS "${WASM_FLAGS} -s ERROR_ON_MISSING_LIBRARIES=1") + + if (CMAKE_BUILD_TYPE MATCHES DEBUG) + set(WASM_FLAGS "${WASM_FLAGS} -g4") # generate debug information + set(WASM_FLAGS "${WASM_FLAGS} -s ASSERTIONS=2") # more runtime checks + else() + set(WASM_FLAGS "${WASM_FLAGS} -Os") # optimize for web (speed and size) + endif() + + set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module") + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${WASM_FLAGS}") # not always clear which flags are for the compiler and which one are for the linker -> pass them all to the linker too + # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"'") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_STACK=128000000") + + add_definitions(-DORTHANC_ENABLE_WASM=1) + set(ORTHANC_SANDBOXED ON) + +elseif (ENABLE_QT OR ENABLE_SDL) + + set(ENABLE_NATIVE ON) + set(ORTHANC_SANDBOXED OFF) + set(ENABLE_CRYPTO_OPTIONS ON) + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_WEB_CLIENT ON) + +endif() + + +##################################################################### +## Configuration for Orthanc +##################################################################### + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.4.1") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + + +##################################################################### +## Build a static library containing the Orthanc Stone framework +##################################################################### + +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneConfiguration.cmake) + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +##################################################################### +## Build all the sample applications +##################################################################### + +include_directories(${ORTHANC_STONE_ROOT}) + +list(APPEND RTVIEWERDEMO_APPLICATION_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h + ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h + ) + +if (ENABLE_WASM) + list(APPEND RTVIEWERDEMO_APPLICATION_SOURCES + ${STONE_WASM_SOURCES} + ) +endif() + +add_executable(RtViewerDemo + main.cpp + ${RTVIEWERDEMO_APPLICATION_SOURCES} +) +set_target_properties(RtViewerDemo PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=3) +target_include_directories(RtViewerDemo PRIVATE ${ORTHANC_STONE_ROOT}) +target_link_libraries(RtViewerDemo OrthancStone) + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/build-sdl-msvc15.ps1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/build-sdl-msvc15.ps1 Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,24 @@ +if (-not (Test-Path "build-sdl-msvc15")) { + mkdir -p "build-sdl-msvc15" +} + +cd build-sdl-msvc15 + +cmake -G "Visual Studio 15 2017 Win64" -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DSTONE_SOURCES_DIR="$($pwd)\..\..\..\.." -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\..\..\..\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON .. + +if (!$?) { + Write-Error 'cmake configuration failed' -ErrorAction Stop +} + +cmake --build . --target RtViewerDemo --config Debug + +if (!$?) { + Write-Error 'cmake build failed' -ErrorAction Stop +} + +cd Debug + +.\RtViewerDemo.exe --ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 + + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/build-wasm.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/build-wasm.sh Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,29 @@ +#!/bin/bash +# +# usage: +# build-wasm BUILD_TYPE +# where BUILD_TYPE is Debug, RelWithDebInfo or Release + +set -e + +buildType=${1:-Debug} + +currentDir=$(pwd) +currentDirAbs=$(realpath $currentDir) + +mkdir -p build-wasm +cd build-wasm + +source ~/apps/emsdk/emsdk_env.sh +cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake \ +-DCMAKE_BUILD_TYPE=$buildType -DSTONE_SOURCES_DIR=$currentDirAbs/../../../../orthanc-stone \ +-DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDirAbs/../../../../orthanc \ +-DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON + +ninja $target + +echo "-- building the web application -- " +cd $currentDir +./build-web.sh + +echo "Launch start-serving-files.sh to access the web sample application locally" diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/build-web.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/build-web.sh Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +target=${1:-all} +# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/ + +currentDir=$(pwd) +samplesRootDir=$(pwd) + +tscOutput=$samplesRootDir/build-tsc-output/ +outputDir=$samplesRootDir/build-web/ +mkdir -p "$outputDir" + +# files used by all single files samples +cp "$samplesRootDir/index.html" "$outputDir" +cp "$samplesRootDir/samples-styles.css" "$outputDir" + +# build rt-viewer-demo +cp $samplesRootDir/rt-viewer-demo.html $outputDir +tsc --project $samplesRootDir/rt-viewer-demo.tsconfig.json --outDir "$tscOutput" +browserify \ + "$tscOutput/orthanc-stone/Platforms/Wasm/logger.js" \ + "$tscOutput/orthanc-stone/Platforms/Wasm/stone-framework-loader.js" \ + "$tscOutput/orthanc-stone/Platforms/Wasm/wasm-application-runner.js" \ + "$tscOutput/orthanc-stone/Platforms/Wasm/wasm-viewport.js" \ + "$tscOutput/rt-viewer-sample/rt-viewer-demo.js" \ + -o "$outputDir/app-rt-viewer-demo.js" +cp "$currentDir/build-wasm/RtViewerDemo.js" $outputDir +cp "$currentDir/build-wasm/RtViewerDemo.wasm" $outputDir + +cd $currentDir diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/index.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,20 @@ + + + + + + + + + + + + Wasm Samples + + + + + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/main.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,893 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "Applications/IStoneApplication.h" +#include "Framework/Widgets/WorldSceneWidget.h" +#include "Framework/Widgets/LayoutWidget.h" + +#if ORTHANC_ENABLE_WASM==1 + #include "Platforms/Wasm/WasmPlatformApplicationAdapter.h" + #include "Platforms/Wasm/Defaults.h" + #include "Platforms/Wasm/WasmViewport.h" +#endif + +#if ORTHANC_ENABLE_QT==1 + #include "Qt/SampleMainWindow.h" + #include "Qt/SampleMainWindowWithButtons.h" +#endif + +#include "Framework/Layers/DicomSeriesVolumeSlicer.h" +#include "Framework/Widgets/SliceViewerWidget.h" +#include "Framework/Volumes/StructureSetLoader.h" + +#include +#include +#include + +#include +#include "Framework/dev.h" +#include "Framework/Widgets/LayoutWidget.h" +#include "Framework/Layers/DicomStructureSetSlicer.h" + +namespace OrthancStone +{ + namespace Samples + { + class RtViewerDemoBaseApplication : public IStoneApplication + { + protected: + // ownership is transferred to the application context +#ifndef RESTORE_NON_RTVIEWERDEMO_BEHAVIOR + LayoutWidget* mainWidget_; +#else + WorldSceneWidget* mainWidget_; +#endif + + public: + virtual void Initialize(StoneApplicationContext* context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE + { + } + + virtual std::string GetTitle() const ORTHANC_OVERRIDE + { + return "Stone of Orthanc - Sample"; + } + + /** + * In the basic samples, the commands are handled by the platform adapter and NOT + * by the application handler + */ + virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE {}; + + + virtual void Finalize() ORTHANC_OVERRIDE {} + virtual IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainWidget_;} + +#if ORTHANC_ENABLE_WASM==1 + // default implementations for a single canvas named "canvas" in the HTML and an empty WasmApplicationAdapter + + virtual void InitializeWasm() ORTHANC_OVERRIDE + { + AttachWidgetToWasmViewport("canvas", mainWidget_); + } + + virtual WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(MessageBroker& broker) + { + return new WasmPlatformApplicationAdapter(broker, *this); + } +#endif + + }; + + // this application actually works in Qt and WASM + class RtViewerDemoBaseSingleCanvasWithButtonsApplication : public RtViewerDemoBaseApplication + { +public: + virtual void OnPushButton1Clicked() {} + virtual void OnPushButton2Clicked() {} + virtual void OnTool1Clicked() {} + virtual void OnTool2Clicked() {} + + virtual void GetButtonNames(std::string& pushButton1, + std::string& pushButton2, + std::string& tool1, + std::string& tool2 + ) { + pushButton1 = "action1"; + pushButton2 = "action2"; + tool1 = "tool1"; + tool2 = "tool2"; + } + +#if ORTHANC_ENABLE_QT==1 + virtual QStoneMainWindow* CreateQtMainWindow() { + return new SampleMainWindowWithButtons(dynamic_cast(*context_), *this); + } +#endif + + }; + + // this application actually works in SDL and WASM + class RtViewerDemoBaseApplicationSingleCanvas : public RtViewerDemoBaseApplication + { +public: + +#if ORTHANC_ENABLE_QT==1 + virtual QStoneMainWindow* CreateQtMainWindow() { + return new SampleMainWindow(dynamic_cast(*context_), *this); + } +#endif + }; + } +} + + + +namespace OrthancStone +{ + namespace Samples + { + template + void ReadDistributionInternal(std::vector& distribution, + const Orthanc::ImageAccessor& image) + { + const unsigned int width = image.GetWidth(); + const unsigned int height = image.GetHeight(); + + distribution.resize(width * height); + size_t pos = 0; + + for (unsigned int y = 0; y < height; y++) + { + for (unsigned int x = 0; x < width; x++, pos++) + { + distribution[pos] = Orthanc::ImageTraits::GetFloatPixel(image, x, y); + } + } + } + + void ReadDistribution(std::vector& distribution, + const Orthanc::ImageAccessor& image) + { + switch (image.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + ReadDistributionInternal(distribution, image); + break; + + case Orthanc::PixelFormat_Grayscale16: + ReadDistributionInternal(distribution, image); + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + ReadDistributionInternal(distribution, image); + break; + + case Orthanc::PixelFormat_Grayscale32: + ReadDistributionInternal(distribution, image); + break; + + case Orthanc::PixelFormat_Grayscale64: + ReadDistributionInternal(distribution, image); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + class DoseInteractor : public VolumeImageInteractor + { + private: + SliceViewerWidget& widget_; + size_t layer_; + DicomFrameConverter converter_; + + + + protected: + virtual void NotifySliceChange(const ISlicedVolume& slicedVolume, + const size_t& sliceIndex, + const Slice& slice) + { + converter_ = slice.GetConverter(); + + #if 0 + const OrthancVolumeImage& volume = dynamic_cast(slicedVolume); + + RenderStyle s = widget_.GetLayerStyle(layer_); + + if (volume.FitWindowingToRange(s, slice.GetConverter())) + { + printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); + widget_.SetLayerStyle(layer_, s); + } + #endif + } + + virtual void NotifyVolumeReady(const ISlicedVolume& slicedVolume) + { + const float percentile = 0.01f; + const OrthancVolumeImage& volume = dynamic_cast(slicedVolume); + + std::vector distribution; + ReadDistribution(distribution, volume.GetImage().GetInternalImage()); + std::sort(distribution.begin(), distribution.end()); + + int start = static_cast(std::ceil(distribution.size() * percentile)); + int end = static_cast(std::floor(distribution.size() * (1.0f - percentile))); + + float a = 0; + float b = 0; + + if (start < end && + start >= 0 && + end < static_cast(distribution.size())) + { + a = distribution[start]; + b = distribution[end]; + } + else if (!distribution.empty()) + { + // Too small distribution: Use full range + a = distribution.front(); + b = distribution.back(); + } + + //printf("%f %f\n", a, b); + + RenderStyle s = widget_.GetLayerStyle(layer_); + s.windowing_ = ImageWindowing_Custom; + s.customWindowCenter_ = static_cast(converter_.Apply((a + b) / 2.0f)); + s.customWindowWidth_ = static_cast(converter_.Apply(b - a)); + + // 96.210556 => 192.421112 + widget_.SetLayerStyle(layer_, s); + printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); + } + + public: + DoseInteractor(MessageBroker& broker, OrthancVolumeImage& volume, + SliceViewerWidget& widget, + VolumeProjection projection, + size_t layer) : + VolumeImageInteractor(broker, volume, widget, projection), + widget_(widget), + layer_(layer) + { + } + }; + + class RtViewerDemoApplication : + public RtViewerDemoBaseApplicationSingleCanvas, + public IObserver + { + public: + std::vector > doseCtWidgetLayerPairs_; + std::list interactors_; + + class Interactor : public IWorldSceneInteractor + { + private: + RtViewerDemoApplication& application_; + + public: + Interactor(RtViewerDemoApplication& application) : + application_(application) + { + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + IStatusBar* statusBar, + const std::vector& displayTouches) + { + return NULL; + } + + virtual void MouseOver(CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) + { + if (statusBar != NULL) + { + Vector p = dynamic_cast(widget).GetSlice().MapSliceToWorldCoordinates(x, y); + + char buf[64]; + sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", + p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); + statusBar->SetMessage(buf); + } + } + + virtual void MouseWheel(WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1); + + switch (direction) + { + case MouseWheelDirection_Up: + application_.OffsetSlice(-scale); + break; + + case MouseWheelDirection_Down: + application_.OffsetSlice(scale); + break; + + default: + break; + } + } + + virtual void KeyPressed(WorldSceneWidget& widget, + KeyboardKeys key, + char keyChar, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + switch (keyChar) + { + case 's': + // TODO: recursively traverse children + widget.FitContent(); + break; + + default: + break; + } + } + }; + + void OffsetSlice(int offset) + { + if (source_ != NULL) + { + int slice = static_cast(slice_) + offset; + + if (slice < 0) + { + slice = 0; + } + + if (slice >= static_cast(source_->GetSliceCount())) + { + slice = static_cast(source_->GetSliceCount()) - 1; + } + + if (slice != static_cast(slice_)) + { + SetSlice(slice); + } + } + } + + + SliceViewerWidget& GetMainWidget() + { + return *dynamic_cast(mainWidget_); + } + + + void SetSlice(size_t index) + { + if (source_ != NULL && + index < source_->GetSliceCount()) + { + slice_ = static_cast(index); + +#if 1 + GetMainWidget().SetSlice(source_->GetSlice(slice_).GetGeometry()); +#else + // TEST for scene extents - Rotate the axes + double a = 15.0 / 180.0 * boost::math::constants::pi(); + +#if 1 + Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); + Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0); +#else + // Flip the normal + Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); + Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0); +#endif + + SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y); + widget_->SetSlice(s); +#endif + } + } + + + void OnMainWidgetGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message) + { + // Once the geometry of the series is downloaded from Orthanc, + // display its middle slice, and adapt the viewport to fit this + // slice + if (source_ == &message.GetOrigin()) + { + SetSlice(source_->GetSliceCount() / 2); + } + + GetMainWidget().FitContent(); + } + + DicomFrameConverter converter_; + + void OnSliceContentChangedMessage(const ISlicedVolume::SliceContentChangedMessage& message) + { + converter_ = message.GetSlice().GetConverter(); + } + + void OnVolumeReadyMessage(const ISlicedVolume::VolumeReadyMessage& message) + { + const float percentile = 0.01f; + + auto& slicedVolume = message.GetOrigin(); + const OrthancVolumeImage& volume = dynamic_cast(slicedVolume); + + std::vector distribution; + ReadDistribution(distribution, volume.GetImage().GetInternalImage()); + std::sort(distribution.begin(), distribution.end()); + + int start = static_cast(std::ceil(distribution.size() * percentile)); + int end = static_cast(std::floor(distribution.size() * (1.0f - percentile))); + + float a = 0; + float b = 0; + + if (start < end && + start >= 0 && + end < static_cast(distribution.size())) + { + a = distribution[start]; + b = distribution[end]; + } + else if (!distribution.empty()) + { + // Too small distribution: Use full range + a = distribution.front(); + b = distribution.back(); + } + + //printf("WINDOWING %f %f\n", a, b); + + for (const auto& pair : doseCtWidgetLayerPairs_) + { + auto widget = pair.first; + auto layer = pair.second; + RenderStyle s = widget->GetLayerStyle(layer); + s.windowing_ = ImageWindowing_Custom; + s.customWindowCenter_ = static_cast(converter_.Apply((a + b) / 2.0f)); + s.customWindowWidth_ = static_cast(converter_.Apply(b - a)); + + // 96.210556 => 192.421112 + widget->SetLayerStyle(layer, s); + printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); + } + } + + + + size_t AddDoseLayer(SliceViewerWidget& widget, + OrthancVolumeImage& volume, VolumeProjection projection); + + void AddStructLayer( + SliceViewerWidget& widget, StructureSetLoader& loader); + + SliceViewerWidget* CreateDoseCtWidget( + std::unique_ptr& ct, + std::unique_ptr& dose, + std::unique_ptr& structLoader, + VolumeProjection projection); + + void AddCtLayer(SliceViewerWidget& widget, OrthancVolumeImage& volume); + + std::unique_ptr mainWidgetInteractor_; + const DicomSeriesVolumeSlicer* source_; + unsigned int slice_; + + std::string ctSeries_; + std::string doseInstance_; + std::string doseSeries_; + std::string structInstance_; + std::unique_ptr dose_; + std::unique_ptr ct_; + std::unique_ptr struct_; + + public: + RtViewerDemoApplication(MessageBroker& broker) : + IObserver(broker), + source_(NULL), + slice_(0) + { + } + + /* + dev options on bgo xps15 + + COMMAND LINE + --ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 + + URL PARAMETERS + ?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 + + */ + + void ParseParameters(const boost::program_options::variables_map& parameters) + { + // Generic + { + if (parameters.count("verbose")) + { + Orthanc::Logging::EnableInfoLevel(true); + LOG(INFO) << "Verbose logs (info) are enabled"; + } + } + + { + if (parameters.count("trace")) + { + LOG(INFO) << "parameters.count(\"trace\") != 0"; + Orthanc::Logging::EnableTraceLevel(true); + VLOG(1) << "Trace logs (debug) are enabled"; + } + } + + // CT series + { + + if (parameters.count("ct-series") != 1) + { + LOG(ERROR) << "There must be exactly one CT series specified"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + ctSeries_ = parameters["ct-series"].as(); + } + + // RTDOSE + { + if (parameters.count("dose-instance") == 1) + { + doseInstance_ = parameters["dose-instance"].as(); + } + else + { +#ifdef BGO_NOT_IMPLEMENTED_YET + // Dose series + if (parameters.count("dose-series") != 1) + { + LOG(ERROR) << "the RTDOSE series is missing"; + throw Orthanc::OrthancException( + Orthanc::ErrorCode_ParameterOutOfRange); + } + doseSeries_ = parameters["ct"].as(); +#endif + LOG(ERROR) << "the RTSTRUCT instance is missing"; + throw Orthanc::OrthancException( + Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + // RTSTRUCT + { + if (parameters.count("struct-instance") == 1) + { + structInstance_ = parameters["struct-instance"].as(); + } + else + { +#ifdef BGO_NOT_IMPLEMENTED_YET + // Struct series + if (parameters.count("struct-series") != 1) + { + LOG(ERROR) << "the RTSTRUCT series is missing"; + throw Orthanc::OrthancException( + Orthanc::ErrorCode_ParameterOutOfRange); + } + structSeries_ = parameters["struct-series"].as(); +#endif + LOG(ERROR) << "the RTSTRUCT instance is missing"; + throw Orthanc::OrthancException( + Orthanc::ErrorCode_ParameterOutOfRange); + } + } + } + + virtual void DeclareStartupOptions( + boost::program_options::options_description& options) + { + boost::program_options::options_description generic( + "RtViewerDemo options. Please note that some of these options " + "are mutually exclusive"); + generic.add_options() + ("ct-series", boost::program_options::value(), + "Orthanc ID of the CT series") + ("dose-instance", boost::program_options::value(), + "Orthanc ID of the RTDOSE instance (incompatible with dose-series)") + ("dose-series", boost::program_options::value(), + "NOT IMPLEMENTED YET. Orthanc ID of the RTDOSE series (incompatible" + " with dose-instance)") + ("struct-instance", boost::program_options::value(), + "Orthanc ID of the RTSTRUCT instance (incompatible with struct-" + "series)") + ("struct-series", boost::program_options::value(), + "NOT IMPLEMENTED YET. Orthanc ID of the RTSTRUCT (incompatible with" + " struct-instance)") + ("smooth", boost::program_options::value()->default_value(true), + "Enable bilinear image smoothing") + ; + + options.add(generic); + } + + virtual void Initialize( + StoneApplicationContext* context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + ParseParameters(parameters); + + context_ = context; + + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); + + if (!ctSeries_.empty()) + { + printf("CT = [%s]\n", ctSeries_.c_str()); + + ct_.reset(new OrthancStone::OrthancVolumeImage( + IObserver::GetBroker(), context->GetOrthancApiClient(), false)); + ct_->ScheduleLoadSeries(ctSeries_); + //ct_->ScheduleLoadSeries( + // "a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); + //ct_->ScheduleLoadSeries( + // "03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); + } + + if (!doseSeries_.empty() || + !doseInstance_.empty()) + { + dose_.reset(new OrthancStone::OrthancVolumeImage( + IObserver::GetBroker(), context->GetOrthancApiClient(), true)); + + + dose_->RegisterObserverCallback( + new Callable + (*this, &RtViewerDemoApplication::OnVolumeReadyMessage)); + + dose_->RegisterObserverCallback( + new Callable + (*this, &RtViewerDemoApplication::OnSliceContentChangedMessage)); + + if (doseInstance_.empty()) + { + dose_->ScheduleLoadSeries(doseSeries_); + } + else + { + dose_->ScheduleLoadInstance(doseInstance_); + } + + //dose_->ScheduleLoadInstance( + //"830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // 1 + //dose_->ScheduleLoadInstance( + //"269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c"); //0522c0001 TCIA + } + + if (!structInstance_.empty()) + { + struct_.reset(new OrthancStone::StructureSetLoader( + IObserver::GetBroker(), context->GetOrthancApiClient())); + + struct_->ScheduleLoadInstance(structInstance_); + + //struct_->ScheduleLoadInstance( + //"54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); + //struct_->ScheduleLoadInstance( + //"17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA + } + + mainWidget_ = new LayoutWidget("main-layout"); + mainWidget_->SetBackgroundColor(0, 0, 0); + mainWidget_->SetBackgroundCleared(true); + mainWidget_->SetPadding(0); + + auto axialWidget = CreateDoseCtWidget + (ct_, dose_, struct_, OrthancStone::VolumeProjection_Axial); + mainWidget_->AddWidget(axialWidget); + + std::unique_ptr subLayout( + new OrthancStone::LayoutWidget("main-layout")); + subLayout->SetVertical(); + subLayout->SetPadding(5); + + auto coronalWidget = CreateDoseCtWidget + (ct_, dose_, struct_, OrthancStone::VolumeProjection_Coronal); + subLayout->AddWidget(coronalWidget); + + auto sagittalWidget = CreateDoseCtWidget + (ct_, dose_, struct_, OrthancStone::VolumeProjection_Sagittal); + subLayout->AddWidget(sagittalWidget); + + mainWidget_->AddWidget(subLayout.release()); + } + }; + + + size_t RtViewerDemoApplication::AddDoseLayer( + SliceViewerWidget& widget, + OrthancVolumeImage& volume, VolumeProjection projection) + { + size_t layer = widget.AddLayer( + new VolumeImageMPRSlicer(IObserver::GetBroker(), volume)); + + RenderStyle s; + //s.drawGrid_ = true; + s.SetColor(255, 0, 0); // Draw missing PET layer in red + s.alpha_ = 0.3f; + s.applyLut_ = true; + s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; + s.interpolation_ = ImageInterpolation_Bilinear; + widget.SetLayerStyle(layer, s); + + return layer; + } + + void RtViewerDemoApplication::AddStructLayer( + SliceViewerWidget& widget, StructureSetLoader& loader) + { + widget.AddLayer(new DicomStructureSetSlicer( + IObserver::GetBroker(), loader)); + } + + SliceViewerWidget* RtViewerDemoApplication::CreateDoseCtWidget( + std::unique_ptr& ct, + std::unique_ptr& dose, + std::unique_ptr& structLoader, + VolumeProjection projection) + { + std::unique_ptr widget( + new OrthancStone::SliceViewerWidget(IObserver::GetBroker(), + "ct-dose-widget")); + + if (ct.get() != NULL) + { + AddCtLayer(*widget, *ct); + } + + if (dose.get() != NULL) + { + size_t layer = AddDoseLayer(*widget, *dose, projection); + + // we need to store the dose rendering widget because we'll update them + // according to various asynchronous events + doseCtWidgetLayerPairs_.push_back(std::make_pair(widget.get(), layer)); +#if 0 + interactors_.push_back(new VolumeImageInteractor( + IObserver::GetBroker(), *dose, *widget, projection)); +#else + interactors_.push_back(new DoseInteractor( + IObserver::GetBroker(), *dose, *widget, projection, layer)); +#endif + } + else if (ct.get() != NULL) + { + interactors_.push_back( + new VolumeImageInteractor( + IObserver::GetBroker(), *ct, *widget, projection)); + } + + if (structLoader.get() != NULL) + { + AddStructLayer(*widget, *structLoader); + } + + return widget.release(); + } + + void RtViewerDemoApplication::AddCtLayer( + SliceViewerWidget& widget, + OrthancVolumeImage& volume) + { + size_t layer = widget.AddLayer( + new VolumeImageMPRSlicer(IObserver::GetBroker(), volume)); + + RenderStyle s; + //s.drawGrid_ = true; + s.alpha_ = 1; + s.windowing_ = ImageWindowing_Bone; + widget.SetLayerStyle(layer, s); + } + } +} + + + +#if ORTHANC_ENABLE_WASM==1 + +#include "Platforms/Wasm/WasmWebService.h" +#include "Platforms/Wasm/WasmViewport.h" + +#include + +//#include "SampleList.h" + + +OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) +{ + return new OrthancStone::Samples::RtViewerDemoApplication(broker); +} + +OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, OrthancStone::IStoneApplication* application) +{ + return dynamic_cast(application)->CreateWasmApplicationAdapter(broker); +} + +#else + +//#include "SampleList.h" +#if ORTHANC_ENABLE_SDL==1 +#include "Applications/Sdl/SdlStoneApplicationRunner.h" +#endif +#if ORTHANC_ENABLE_QT==1 +#include "Applications/Qt/SampleQtApplicationRunner.h" +#endif +#include "Framework/Messages/MessageBroker.h" + +int main(int argc, char* argv[]) +{ + OrthancStone::MessageBroker broker; + OrthancStone::Samples::RtViewerDemoApplication sampleStoneApplication(broker); + +#if ORTHANC_ENABLE_SDL==1 + OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(broker, sampleStoneApplication); + return sdlApplicationRunner.Execute(argc, argv); +#endif +#if ORTHANC_ENABLE_QT==1 + OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication); + return qtAppRunner.Execute(argc, argv); +#endif +} + + +#endif + + + + + + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/nginx.local.conf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/nginx.local.conf Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,44 @@ +# Local config to serve the WASM samples static files and reverse proxy Orthanc. +# Uses port 9977 instead of 80. + +# `events` section is mandatory +events { + worker_connections 1024; # Default: 1024 +} + +http { + + # prevent nginx sync issues on OSX + proxy_buffering off; + + server { + listen 9977 default_server; + client_max_body_size 4G; + + # location may have to be adjusted depending on your OS and nginx install + include /etc/nginx/mime.types; + # if not in your system mime.types, add this line to support WASM: + # types { + # application/wasm wasm; + # } + + # serve WASM static files + root build-web/; + location / { + } + + # reverse proxy orthanc + location /orthanc/ { + rewrite /orthanc(.*) $1 break; + proxy_pass http://127.0.0.1:8042; + proxy_set_header Host $http_host; + proxy_set_header my-auth-header good-token; + proxy_request_buffering off; + proxy_max_temp_file_size 0; + client_max_body_size 0; + } + + + } + +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/rt-viewer-demo.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/rt-viewer-demo.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,25 @@ + + + + + + + + + + + + Simple Viewer + + + +
+

RTSTRUCT viewer demonstration

+
+
+ +
+ + + + \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/rt-viewer-demo.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/rt-viewer-demo.ts Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,5 @@ +import { InitializeWasmApplication } from '../../../Platforms/Wasm/wasm-application-runner'; + + +InitializeWasmApplication("RtViewerDemo", "/orthanc"); + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/rt-viewer-demo.tsconfig.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/rt-viewer-demo.tsconfig.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,8 @@ +{ + "extends" : "./tsconfig-samples", + "compilerOptions": { + }, + "include" : [ + "rt-viewer-demo.ts" + ] +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/samples-styles.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/samples-styles.css Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,16 @@ +html, body { + width: 100%; + height: 100%; + margin: 0px; + border: 0; + overflow: hidden; /* Disable scrollbars */ + display: block; /* No floating content on sides */ + background-color: black; + color: white; + font-family: Arial, Helvetica, sans-serif; +} + +canvas { + left:0px; + top:0px; +} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/start-serving-files.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/start-serving-files.sh Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,9 @@ +#!/bin/bash + +sudo nginx -p $(pwd) -c nginx.local.conf + +echo "Please browse to :" + +echo "http://localhost:9977/rt-viewer-demo.html?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9" + +echo "(This requires you have uploaded the correct files to your local Orthanc instance)" diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/stop-serving-files.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/stop-serving-files.sh Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,4 @@ +#!/bin/bash + +sudo nginx -s stop + diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/rt-viewer-demo/tsconfig-samples.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/rt-viewer-demo/tsconfig-samples.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,11 @@ +{ + "extends" : "../../../Platforms/Wasm/tsconfig-stone.json", + "compilerOptions": { + "sourceMap": false, + "lib" : [ + "es2017", + "dom", + "dom.iterable" + ] + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Deprecated/tsconfig-stone.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Deprecated/tsconfig-stone.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,7 @@ +{ + "include" : [ + "../../Platforms/Wasm/stone-framework-loader.ts", + "../../Platforms/Wasm/wasm-application-runner.ts", + "../../Platforms/Wasm/wasm-viewport.ts" + ] +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/EmptyApplication.h --- a/Applications/Samples/EmptyApplication.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "SampleApplicationBase.h" - -#include "../../Framework/Widgets/EmptyWidget.h" - -namespace OrthancStone -{ - namespace Samples - { - class EmptyApplication : public SampleApplicationBase - { - public: - virtual void DeclareStartupOptions(boost::program_options::options_description& options) - { - boost::program_options::options_description generic("Sample options"); - generic.add_options() - ("red", boost::program_options::value()->default_value(255), "Background color: red channel") - ("green", boost::program_options::value()->default_value(0), "Background color: green channel") - ("blue", boost::program_options::value()->default_value(0), "Background color: blue channel") - ; - - options.add(generic); - } - - virtual void Initialize(IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - int red = parameters["red"].as(); - int green = parameters["green"].as(); - int blue = parameters["blue"].as(); - - context_->SetCentralWidget(new EmptyWidget(red, green, blue)); - } - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/LayoutPetCtFusionApplication.h --- a/Applications/Samples/LayoutPetCtFusionApplication.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,398 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "SampleInteractor.h" - -#include "../../Framework/Layers/ReferenceLineFactory.h" -#include "../../Framework/Layers/DicomStructureSetSlicer.h" -#include "../../Framework/Widgets/LayoutWidget.h" - -#include - -namespace OrthancStone -{ - namespace Samples - { - class LayoutPetCtFusionApplication : - public SampleApplicationBase, - public LayeredSceneWidget::ISliceObserver, - public WorldSceneWidget::IWorldObserver - { - private: - class Interactor : public SampleInteractor - { - private: - LayoutPetCtFusionApplication& that_; - - public: - Interactor(LayoutPetCtFusionApplication& that, - VolumeImage& volume, - VolumeProjection projection, - bool reverse) : - SampleInteractor(volume, projection, reverse), - that_(that) - { - } - - virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, - const SliceGeometry& slice, - const ViewportGeometry& view, - MouseButton button, - double x, - double y, - IStatusBar* statusBar) - { - if (button == MouseButton_Left) - { - // Center the sibling views over the clicked point - Vector p = slice.MapSliceToWorldCoordinates(x, y); - - if (statusBar != NULL) - { - char buf[64]; - sprintf(buf, "Click on coordinates (%.02f,%.02f,%.02f) in cm", p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); - statusBar->SetMessage(buf); - } - - that_.interactorAxial_->LookupSliceContainingPoint(*that_.ctAxial_, p); - that_.interactorCoronal_->LookupSliceContainingPoint(*that_.ctCoronal_, p); - that_.interactorSagittal_->LookupSliceContainingPoint(*that_.ctSagittal_, p); - } - - return NULL; - } - - virtual void KeyPressed(WorldSceneWidget& widget, - char key, - KeyboardModifiers modifiers, - IStatusBar* statusBar) - { - if (key == 's') - { - that_.FitContent(); - } - } - }; - - bool processingEvent_; - Interactor* interactorAxial_; - Interactor* interactorCoronal_; - Interactor* interactorSagittal_; - LayeredSceneWidget* ctAxial_; - LayeredSceneWidget* ctCoronal_; - LayeredSceneWidget* ctSagittal_; - LayeredSceneWidget* petAxial_; - LayeredSceneWidget* petCoronal_; - LayeredSceneWidget* petSagittal_; - LayeredSceneWidget* fusionAxial_; - LayeredSceneWidget* fusionCoronal_; - LayeredSceneWidget* fusionSagittal_; - - - void FitContent() - { - petAxial_->FitContent(); - petCoronal_->FitContent(); - petSagittal_->FitContent(); - } - - - void AddLayer(LayeredSceneWidget& widget, - VolumeImage& volume, - bool isCt) - { - size_t layer; - widget.AddLayer(layer, new VolumeImage::LayerFactory(volume)); - - if (isCt) - { - RenderStyle style; - style.windowing_ = ImageWindowing_Bone; - widget.SetLayerStyle(layer, style); - } - else - { - RenderStyle style; - style.applyLut_ = true; - style.alpha_ = (layer == 0 ? 1.0f : 0.5f); - widget.SetLayerStyle(layer, style); - } - } - - - void ConnectSiblingLocations(LayeredSceneWidget& axial, - LayeredSceneWidget& coronal, - LayeredSceneWidget& sagittal) - { - ReferenceLineFactory::Configure(axial, coronal); - ReferenceLineFactory::Configure(axial, sagittal); - ReferenceLineFactory::Configure(coronal, sagittal); - } - - - void SynchronizeView(const WorldSceneWidget& source, - const ViewportGeometry& view, - LayeredSceneWidget& widget1, - LayeredSceneWidget& widget2, - LayeredSceneWidget& widget3) - { - if (&source == &widget1 || - &source == &widget2 || - &source == &widget3) - { - if (&source != &widget1) - { - widget1.SetView(view); - } - - if (&source != &widget2) - { - widget2.SetView(view); - } - - if (&source != &widget3) - { - widget3.SetView(view); - } - } - } - - - void SynchronizeSlice(const LayeredSceneWidget& source, - const SliceGeometry& slice, - LayeredSceneWidget& widget1, - LayeredSceneWidget& widget2, - LayeredSceneWidget& widget3) - { - if (&source == &widget1 || - &source == &widget2 || - &source == &widget3) - { - if (&source != &widget1) - { - widget1.SetSlice(slice); - } - - if (&source != &widget2) - { - widget2.SetSlice(slice); - } - - if (&source != &widget3) - { - widget3.SetSlice(slice); - } - } - } - - - LayeredSceneWidget* CreateWidget() - { - std::unique_ptr widget(new LayeredSceneWidget); - widget->Register(dynamic_cast(*this)); - widget->Register(dynamic_cast(*this)); - return widget.release(); - } - - - void CreateLayout(BasicApplicationContext& context) - { - std::unique_ptr layout(new OrthancStone::LayoutWidget); - layout->SetBackgroundCleared(true); - //layout->SetBackgroundColor(255,0,0); - layout->SetPadding(5); - - OrthancStone::LayoutWidget& layoutA = dynamic_cast - (layout->AddWidget(new OrthancStone::LayoutWidget)); - layoutA.SetPadding(0, 0, 0, 0, 5); - layoutA.SetVertical(); - petAxial_ = &dynamic_cast(layoutA.AddWidget(CreateWidget())); - OrthancStone::LayoutWidget& layoutA2 = dynamic_cast - (layoutA.AddWidget(new OrthancStone::LayoutWidget)); - layoutA2.SetPadding(0, 0, 0, 0, 5); - petSagittal_ = &dynamic_cast(layoutA2.AddWidget(CreateWidget())); - petCoronal_ = &dynamic_cast(layoutA2.AddWidget(CreateWidget())); - - OrthancStone::LayoutWidget& layoutB = dynamic_cast - (layout->AddWidget(new OrthancStone::LayoutWidget)); - layoutB.SetPadding(0, 0, 0, 0, 5); - layoutB.SetVertical(); - ctAxial_ = &dynamic_cast(layoutB.AddWidget(CreateWidget())); - OrthancStone::LayoutWidget& layoutB2 = dynamic_cast - (layoutB.AddWidget(new OrthancStone::LayoutWidget)); - layoutB2.SetPadding(0, 0, 0, 0, 5); - ctSagittal_ = &dynamic_cast(layoutB2.AddWidget(CreateWidget())); - ctCoronal_ = &dynamic_cast(layoutB2.AddWidget(CreateWidget())); - - OrthancStone::LayoutWidget& layoutC = dynamic_cast - (layout->AddWidget(new OrthancStone::LayoutWidget)); - layoutC.SetPadding(0, 0, 0, 0, 5); - layoutC.SetVertical(); - fusionAxial_ = &dynamic_cast(layoutC.AddWidget(CreateWidget())); - OrthancStone::LayoutWidget& layoutC2 = dynamic_cast - (layoutC.AddWidget(new OrthancStone::LayoutWidget)); - layoutC2.SetPadding(0, 0, 0, 0, 5); - fusionSagittal_ = &dynamic_cast(layoutC2.AddWidget(CreateWidget())); - fusionCoronal_ = &dynamic_cast(layoutC2.AddWidget(CreateWidget())); - - context.SetCentralWidget(layout.release()); - } - - - public: - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) - { - boost::program_options::options_description generic("Sample options"); - generic.add_options() - ("ct", boost::program_options::value(), - "Orthanc ID of the CT series") - ("pet", boost::program_options::value(), - "Orthanc ID of the PET series") - ("rt", boost::program_options::value(), - "Orthanc ID of the DICOM RT-STRUCT series (optional)") - ("threads", boost::program_options::value()->default_value(3), - "Number of download threads for the CT series") - ; - - options.add(generic); - } - - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - using namespace OrthancStone; - - processingEvent_ = true; - - if (parameters.count("ct") != 1 || - parameters.count("pet") != 1) - { - LOG(ERROR) << "The series ID is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - std::string ct = parameters["ct"].as(); - std::string pet = parameters["pet"].as(); - unsigned int threads = parameters["threads"].as(); - - VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads); - VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1); - - // Take the PET volume as the reference for the slices - interactorAxial_ = &dynamic_cast - (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Axial, false))); - interactorCoronal_ = &dynamic_cast - (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Coronal, false))); - interactorSagittal_ = &dynamic_cast - (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Sagittal, true))); - - CreateLayout(context); - - AddLayer(*ctAxial_, ctVolume, true); - AddLayer(*ctCoronal_, ctVolume, true); - AddLayer(*ctSagittal_, ctVolume, true); - - AddLayer(*petAxial_, petVolume, false); - AddLayer(*petCoronal_, petVolume, false); - AddLayer(*petSagittal_, petVolume, false); - - AddLayer(*fusionAxial_, ctVolume, true); - AddLayer(*fusionAxial_, petVolume, false); - AddLayer(*fusionCoronal_, ctVolume, true); - AddLayer(*fusionCoronal_, petVolume, false); - AddLayer(*fusionSagittal_, ctVolume, true); - AddLayer(*fusionSagittal_, petVolume, false); - - if (parameters.count("rt") == 1) - { - DicomStructureSet& rtStruct = context.AddStructureSet(parameters["rt"].as()); - - Vector p = rtStruct.GetStructureCenter(0); - interactorAxial_->GetCursor().LookupSliceContainingPoint(p); - - ctAxial_->AddLayer(new DicomStructureSetSlicer(rtStruct)); - petAxial_->AddLayer(new DicomStructureSetSlicer(rtStruct)); - fusionAxial_->AddLayer(new DicomStructureSetSlicer(rtStruct)); - } - - ConnectSiblingLocations(*ctAxial_, *ctCoronal_, *ctSagittal_); - ConnectSiblingLocations(*petAxial_, *petCoronal_, *petSagittal_); - ConnectSiblingLocations(*fusionAxial_, *fusionCoronal_, *fusionSagittal_); - - interactorAxial_->AddWidget(*ctAxial_); - interactorAxial_->AddWidget(*petAxial_); - interactorAxial_->AddWidget(*fusionAxial_); - - interactorCoronal_->AddWidget(*ctCoronal_); - interactorCoronal_->AddWidget(*petCoronal_); - interactorCoronal_->AddWidget(*fusionCoronal_); - - interactorSagittal_->AddWidget(*ctSagittal_); - interactorSagittal_->AddWidget(*petSagittal_); - interactorSagittal_->AddWidget(*fusionSagittal_); - - processingEvent_ = false; - - statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode"); - statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); - } - - virtual void NotifySizeChange(const WorldSceneWidget& source, - ViewportGeometry& view) - { - view.FitContent(); - } - - virtual void NotifyViewChange(const WorldSceneWidget& source, - const ViewportGeometry& view) - { - if (!processingEvent_) // Avoid reentrant calls - { - processingEvent_ = true; - - SynchronizeView(source, view, *ctAxial_, *petAxial_, *fusionAxial_); - SynchronizeView(source, view, *ctCoronal_, *petCoronal_, *fusionCoronal_); - SynchronizeView(source, view, *ctSagittal_, *petSagittal_, *fusionSagittal_); - - processingEvent_ = false; - } - } - - virtual void NotifySliceContentChange(const LayeredSceneWidget& source, - const SliceGeometry& slice) - { - if (!processingEvent_) // Avoid reentrant calls - { - processingEvent_ = true; - - SynchronizeSlice(source, slice, *ctAxial_, *petAxial_, *fusionAxial_); - SynchronizeSlice(source, slice, *ctCoronal_, *petCoronal_, *fusionCoronal_); - SynchronizeSlice(source, slice, *ctSagittal_, *petSagittal_, *fusionSagittal_); - - processingEvent_ = false; - } - } - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Qt/SampleMainWindow.cpp --- a/Applications/Samples/Qt/SampleMainWindow.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "SampleMainWindow.h" - -/** - * Don't use "ui_MainWindow.h" instead of below, as - * this makes CMake unable to detect when the UI file changes. - **/ -#include -#include "../../Applications/Samples/SampleApplicationBase.h" - -namespace OrthancStone -{ - namespace Samples - { - - SampleMainWindow::SampleMainWindow( - OrthancStone::NativeStoneApplicationContext& context, - OrthancStone::Samples::SampleSingleCanvasApplicationBase& stoneSampleApplication, - QWidget *parent) : - QStoneMainWindow(context, parent), - ui_(new Ui::SampleMainWindow), - stoneSampleApplication_(stoneSampleApplication) - { - ui_->setupUi(this); - SetCentralStoneWidget(*ui_->cairoCentralWidget); - } - - SampleMainWindow::~SampleMainWindow() - { - delete ui_; - } - - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Qt/SampleMainWindow.h --- a/Applications/Samples/Qt/SampleMainWindow.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ -#pragma once - -#include "../../Qt/QCairoWidget.h" -#include "../../Qt/QStoneMainWindow.h" - -namespace Ui -{ - class SampleMainWindow; -} - -namespace OrthancStone -{ - namespace Samples - { - - class SampleSingleCanvasApplicationBase; - - class SampleMainWindow : public QStoneMainWindow - { - Q_OBJECT - - private: - Ui::SampleMainWindow* ui_; - SampleSingleCanvasApplicationBase& stoneSampleApplication_; - - public: - explicit SampleMainWindow(OrthancStone::NativeStoneApplicationContext& context, SampleSingleCanvasApplicationBase& stoneSampleApplication, QWidget *parent = 0); - ~SampleMainWindow(); - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Qt/SampleMainWindow.ui --- a/Applications/Samples/Qt/SampleMainWindow.ui Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ - - - SampleMainWindow - - - - 0 - 0 - 903 - 634 - - - - - 500 - 300 - - - - - 500 - 300 - - - - Stone of Orthanc - - - Qt::LeftToRight - - - - - 0 - 0 - - - - Qt::LeftToRight - - - - QLayout::SetDefaultConstraint - - - - - - 0 - 500 - - - - - - - - - - 0 - 0 - 903 - 22 - - - - - Test - - - - - - - - - QCairoWidget - QGraphicsView -
QCairoWidget.h
-
-
- - -
diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Qt/SampleMainWindowWithButtons.cpp --- a/Applications/Samples/Qt/SampleMainWindowWithButtons.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "SampleMainWindow.h" - -/** - * Don't use "ui_MainWindow.h" instead of below, as - * this makes CMake unable to detect when the UI file changes. - **/ -#include -#include "../../Applications/Samples/SampleApplicationBase.h" - -namespace OrthancStone -{ - namespace Samples - { - - SampleMainWindowWithButtons::SampleMainWindowWithButtons( - OrthancStone::NativeStoneApplicationContext& context, - OrthancStone::Samples::SampleSingleCanvasWithButtonsApplicationBase& stoneSampleApplication, - QWidget *parent) : - QStoneMainWindow(context, parent), - ui_(new Ui::SampleMainWindowWithButtons), - stoneSampleApplication_(stoneSampleApplication) - { - ui_->setupUi(this); - SetCentralStoneWidget(*ui_->cairoCentralWidget); - -#if QT_VERSION >= 0x050000 - connect(ui_->toolButton1, &QToolButton::clicked, this, &SampleMainWindowWithButtons::tool1Clicked); - connect(ui_->toolButton2, &QToolButton::clicked, this, &SampleMainWindowWithButtons::tool2Clicked); - connect(ui_->pushButton1, &QPushButton::clicked, this, &SampleMainWindowWithButtons::pushButton1Clicked); - connect(ui_->pushButton1, &QPushButton::clicked, this, &SampleMainWindowWithButtons::pushButton2Clicked); -#else - connect(ui_->toolButton1, SIGNAL(clicked()), this, SLOT(tool1Clicked())); - connect(ui_->toolButton2, SIGNAL(clicked()), this, SLOT(tool2Clicked())); - connect(ui_->pushButton1, SIGNAL(clicked()), this, SLOT(pushButton1Clicked())); - connect(ui_->pushButton1, SIGNAL(clicked()), this, SLOT(pushButton2Clicked())); -#endif - - std::string pushButton1Name; - std::string pushButton2Name; - std::string tool1Name; - std::string tool2Name; - stoneSampleApplication_.GetButtonNames(pushButton1Name, pushButton2Name, tool1Name, tool2Name); - - ui_->toolButton1->setText(QString::fromStdString(tool1Name)); - ui_->toolButton2->setText(QString::fromStdString(tool2Name)); - ui_->pushButton1->setText(QString::fromStdString(pushButton1Name)); - ui_->pushButton2->setText(QString::fromStdString(pushButton2Name)); - } - - SampleMainWindowWithButtons::~SampleMainWindowWithButtons() - { - delete ui_; - } - - void SampleMainWindowWithButtons::tool1Clicked() - { - stoneSampleApplication_.OnTool1Clicked(); - } - - void SampleMainWindowWithButtons::tool2Clicked() - { - stoneSampleApplication_.OnTool2Clicked(); - } - - void SampleMainWindowWithButtons::pushButton1Clicked() - { - stoneSampleApplication_.OnPushButton1Clicked(); - } - - void SampleMainWindowWithButtons::pushButton2Clicked() - { - stoneSampleApplication_.OnPushButton2Clicked(); - } - - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Qt/SampleMainWindowWithButtons.h --- a/Applications/Samples/Qt/SampleMainWindowWithButtons.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ -#pragma once - -#include "../../Qt/QCairoWidget.h" -#include "../../Qt/QStoneMainWindow.h" - -namespace Ui -{ - class SampleMainWindowWithButtons; -} - -namespace OrthancStone -{ - namespace Samples - { - - class SampleSingleCanvasWithButtonsApplicationBase; - - class SampleMainWindowWithButtons : public QStoneMainWindow - { - Q_OBJECT - - private: - Ui::SampleMainWindowWithButtons* ui_; - SampleSingleCanvasWithButtonsApplicationBase& stoneSampleApplication_; - - public: - explicit SampleMainWindowWithButtons(OrthancStone::NativeStoneApplicationContext& context, SampleSingleCanvasWithButtonsApplicationBase& stoneSampleApplication, QWidget *parent = 0); - ~SampleMainWindowWithButtons(); - - private slots: - void tool1Clicked(); - void tool2Clicked(); - void pushButton1Clicked(); - void pushButton2Clicked(); - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Qt/SampleMainWindowWithButtons.ui --- a/Applications/Samples/Qt/SampleMainWindowWithButtons.ui Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ - - - SampleMainWindowWithButtons - - - - 0 - 0 - 903 - 634 - - - - - 500 - 300 - - - - - 500 - 300 - - - - Stone of Orthanc - - - Qt::LeftToRight - - - - - 0 - 0 - - - - Qt::LeftToRight - - - - QLayout::SetDefaultConstraint - - - - - - 0 - 500 - - - - - - - - - 0 - 100 - - - - - 16777215 - 100 - - - - - - - tool1 - - - - - - - tool2 - - - - - - - action1 - - - - - - - action2 - - - - - - - - - - - - 0 - 0 - 903 - 22 - - - - - Test - - - - - - - - - QCairoWidget - QGraphicsView -
QCairoWidget.h
-
-
- - -
diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Qt/SampleQtApplicationRunner.h --- a/Applications/Samples/Qt/SampleQtApplicationRunner.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../../Qt/QtStoneApplicationRunner.h" - -#if ORTHANC_ENABLE_QT != 1 -#error this file shall be included only with the ORTHANC_ENABLE_QT set to 1 -#endif - -namespace OrthancStone -{ - namespace Samples - { - class SampleQtApplicationRunner : public OrthancStone::QtStoneApplicationRunner - { - protected: - virtual void InitializeMainWindow(OrthancStone::NativeStoneApplicationContext& context) - { - window_.reset(application_.CreateQtMainWindow()); - } - public: - SampleQtApplicationRunner(MessageBroker& broker, - SampleApplicationBase& application) - : OrthancStone::QtStoneApplicationRunner(broker, application) - { - } - - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SampleApplicationBase.h --- a/Applications/Samples/SampleApplicationBase.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../../Applications/IStoneApplication.h" -#include "../../Framework/Deprecated/Widgets/WorldSceneWidget.h" - -#if ORTHANC_ENABLE_WASM==1 -#include "../../Platforms/Wasm/WasmPlatformApplicationAdapter.h" -#include "../../Platforms/Wasm/Defaults.h" -#endif - -#if ORTHANC_ENABLE_QT==1 -#include "Qt/SampleMainWindow.h" -#include "Qt/SampleMainWindowWithButtons.h" -#endif - -namespace OrthancStone -{ - namespace Samples - { - class SampleApplicationBase : public IStoneApplication - { - protected: - // ownership is transferred to the application context - Deprecated::WorldSceneWidget* mainWidget_; - - public: - virtual void Initialize(StoneApplicationContext* context, - Deprecated::IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE - { - } - - virtual std::string GetTitle() const ORTHANC_OVERRIDE - { - return "Stone of Orthanc - Sample"; - } - - /** - * In the basic samples, the commands are handled by the platform adapter and NOT - * by the application handler - */ - virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE {}; - - - virtual void Finalize() ORTHANC_OVERRIDE {} - virtual Deprecated::IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainWidget_;} - -#if ORTHANC_ENABLE_WASM==1 - // default implementations for a single canvas named "canvas" in the HTML and an emtpy WasmApplicationAdapter - - virtual void InitializeWasm() ORTHANC_OVERRIDE - { - AttachWidgetToWasmViewport("canvas", mainWidget_); - } - - virtual WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(MessageBroker& broker) - { - return new WasmPlatformApplicationAdapter(broker, *this); - } -#endif - - }; - - // this application actually works in Qt and WASM - class SampleSingleCanvasWithButtonsApplicationBase : public SampleApplicationBase - { -public: - virtual void OnPushButton1Clicked() {} - virtual void OnPushButton2Clicked() {} - virtual void OnTool1Clicked() {} - virtual void OnTool2Clicked() {} - - virtual void GetButtonNames(std::string& pushButton1, - std::string& pushButton2, - std::string& tool1, - std::string& tool2 - ) { - pushButton1 = "action1"; - pushButton2 = "action2"; - tool1 = "tool1"; - tool2 = "tool2"; - } - -#if ORTHANC_ENABLE_QT==1 - virtual QStoneMainWindow* CreateQtMainWindow() { - return new SampleMainWindowWithButtons(dynamic_cast(*context_), *this); - } -#endif - - }; - - // this application actually works in SDL and WASM - class SampleSingleCanvasApplicationBase : public SampleApplicationBase - { -public: - -#if ORTHANC_ENABLE_QT==1 - virtual QStoneMainWindow* CreateQtMainWindow() { - return new SampleMainWindow(dynamic_cast(*context_), *this); - } -#endif - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SampleInteractor.h --- a/Applications/Samples/SampleInteractor.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "SampleApplicationBase.h" - -#include "../../Framework/Widgets/LayeredSceneWidget.h" -#include "../../Framework/Widgets/IWorldSceneInteractor.h" -#include "../../Framework/Toolbox/ParallelSlicesCursor.h" - -namespace OrthancStone -{ - namespace Samples - { - /** - * This is a basic mouse interactor for sample applications. It - * contains a set of parallel slices in the 3D space. The mouse - * wheel events make the widget change the slice that is - * displayed. - **/ - class SampleInteractor : public IWorldSceneInteractor - { - private: - ParallelSlicesCursor cursor_; - - public: - SampleInteractor(VolumeImage& volume, - VolumeProjection projection, - bool reverse) - { - std::unique_ptr slices(volume.GetGeometry(projection, reverse)); - cursor_.SetGeometry(*slices); - } - - SampleInteractor(ISeriesLoader& series, - bool reverse) - { - if (reverse) - { - std::unique_ptr slices(series.GetGeometry().Reverse()); - cursor_.SetGeometry(*slices); - } - else - { - cursor_.SetGeometry(series.GetGeometry()); - } - } - - SampleInteractor(const ParallelSlices& slices) - { - cursor_.SetGeometry(slices); - } - - ParallelSlicesCursor& GetCursor() - { - return cursor_; - } - - void AddWidget(LayeredSceneWidget& widget) - { - widget.SetInteractor(*this); - widget.SetSlice(cursor_.GetCurrentSlice()); - } - - virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, - const ViewportGeometry& view, - MouseButton button, - double x, - double y, - IStatusBar* statusBar) - { - return NULL; - } - - virtual void MouseOver(CairoContext& context, - WorldSceneWidget& widget, - const ViewportGeometry& view, - double x, - double y, - IStatusBar* statusBar) - { - } - - virtual void MouseWheel(WorldSceneWidget& widget, - MouseWheelDirection direction, - KeyboardModifiers modifiers, - IStatusBar* statusBar) - { - if (cursor_.ApplyWheelEvent(direction, modifiers)) - { - dynamic_cast(widget).SetSlice(cursor_.GetCurrentSlice()); - } - } - - virtual void KeyPressed(WorldSceneWidget& widget, - char key, - KeyboardModifiers modifiers, - IStatusBar* statusBar) - { - } - - void LookupSliceContainingPoint(LayeredSceneWidget& widget, - const Vector& p) - { - if (cursor_.LookupSliceContainingPoint(p)) - { - widget.SetSlice(cursor_.GetCurrentSlice()); - } - } - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SampleList.h --- a/Applications/Samples/SampleList.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -// The macro "ORTHANC_STONE_SAMPLE" must be set by the CMake script - -#if ORTHANC_STONE_SAMPLE == 1 -#include "EmptyApplication.h" -typedef OrthancStone::Samples::EmptyApplication SampleApplication; - -#elif ORTHANC_STONE_SAMPLE == 2 -#include "TestPatternApplication.h" -typedef OrthancStone::Samples::TestPatternApplication SampleApplication; - -#elif ORTHANC_STONE_SAMPLE == 3 -#include "SingleFrameApplication.h" -typedef OrthancStone::Samples::SingleFrameApplication SampleApplication; - -#elif ORTHANC_STONE_SAMPLE == 4 -#include "SingleVolumeApplication.h" -typedef OrthancStone::Samples::SingleVolumeApplication SampleApplication; - -#elif ORTHANC_STONE_SAMPLE == 5 -#include "BasicPetCtFusionApplication.h" -typedef OrthancStone::Samples::BasicPetCtFusionApplication SampleApplication; - -#elif ORTHANC_STONE_SAMPLE == 6 -#include "SynchronizedSeriesApplication.h" -typedef OrthancStone::Samples::SynchronizedSeriesApplication SampleApplication; - -#elif ORTHANC_STONE_SAMPLE == 7 -#include "LayoutPetCtFusionApplication.h" -typedef OrthancStone::Samples::LayoutPetCtFusionApplication SampleApplication; - -#elif ORTHANC_STONE_SAMPLE == 8 -#include "SimpleViewerApplicationSingleFile.h" -typedef OrthancStone::Samples::SimpleViewerApplication SampleApplication; - -#elif ORTHANC_STONE_SAMPLE == 9 -#include "SingleFrameEditorApplication.h" -typedef OrthancStone::Samples::SingleFrameEditorApplication SampleApplication; - -#else -#error Please set the ORTHANC_STONE_SAMPLE macro -#endif diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SampleMainNative.cpp --- a/Applications/Samples/SampleMainNative.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#include "SampleList.h" -#if ORTHANC_ENABLE_SDL==1 -#include "../Sdl/SdlStoneApplicationRunner.h" -#endif -#if ORTHANC_ENABLE_QT==1 -#include "Qt/SampleQtApplicationRunner.h" -#endif -#include "../../Framework/Messages/MessageBroker.h" - -int main(int argc, char* argv[]) -{ - OrthancStone::MessageBroker broker; - SampleApplication sampleStoneApplication(broker); - -#if ORTHANC_ENABLE_SDL==1 - OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(broker, sampleStoneApplication); - return sdlApplicationRunner.Execute(argc, argv); -#endif -#if ORTHANC_ENABLE_QT==1 - OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication); - return qtAppRunner.Execute(argc, argv); -#endif -} - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SampleMainWasm.cpp --- a/Applications/Samples/SampleMainWasm.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "Platforms/Wasm/WasmWebService.h" -#include "Platforms/Wasm/WasmViewport.h" - -#include - -#include "SampleList.h" - - -OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) -{ - return new SampleApplication(broker); -} - -OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, OrthancStone::IStoneApplication* application) -{ - return dynamic_cast(application)->CreateWasmApplicationAdapter(broker); -} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Samples-status.md --- a/Applications/Samples/Samples-status.md Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +0,0 @@ -Executable versions -================ -Generic options ----------------------- -``` -("help", "Display this help and exit") -("verbose", "Be verbose in logs") -("orthanc", boost::program_options::value() - ->default_value("http://localhost:8042/"), - "URL to the Orthanc server") -("username", "Username for the Orthanc server") -("password", "Password for the Orthanc server") -("https-verify", boost::program_options::value() - ->default_value(true), "Check HTTPS certificates") -``` -OrthancStoneSimpleViewer -------------------------------------- -- Options: - ``` - - "studyId", std::string, "Orthanc ID of the study" - ``` -- study loading works OK -- Invert does not work: -``` -void SimpleViewerApplication::ExecuteAction(SimpleViewerApplication::Actions action) - { - // TODO - } -``` - -OrthancStoneSimpleViewerSingleFile -------------------------------------- -- Options: - ``` - - "studyId", std::string, "Orthanc ID of the study" - ``` - -Study loading works. - -The `line` and `circle` buttons work and call this: -``` -virtual void OnTool1Clicked() -{ - currentTool_ = Tools_LineMeasure; -} - -virtual void OnTool2Clicked() -{ - currentTool_ = Tools_CircleMeasure; -} -``` -The `action1` and `action2` buttons are not connected - -The following is displayed in the console at launch time: -``` -W0313 12:20:12.790449 NativeStoneApplicationRunner.cpp:55] Use the key "s" to reinitialize the layout -W0313 12:20:12.790449 NativeStoneApplicationRunner.cpp:55] Use the key "n" to go to next image in the main viewport -``` -However, when looking at `MainWidgetInteractor::KeyPressed` (`SimpleViewerApplicationSingleFile.h:169`), only the following is processed: -- 's': reset layout -- 'l': select line tool -- 'c': select circle tool - -OrthancStoneSingleFrame -------------------------------------- -``` -generic.add_options() -("instance", boost::program_options::value(), -"Orthanc ID of the instance") -("frame", boost::program_options::value() - ->default_value(0), -"Number of the frame, for multi-frame DICOM instances") -("smooth", boost::program_options::value() - ->default_value(true), -"Enable bilinear interpolation to smooth the image"); -``` -only key handled in `KeyPressed` is `s` to call `widget.FitContent()` - - -OrthancStoneSingleFrameEditor -------------------------------------- -``` -generic.add_options() -("instance", boost::program_options::value(), -"Orthanc ID of the instance") -("frame", boost::program_options::value() - ->default_value(0), -"Number of the frame, for multi-frame DICOM instances"); -``` -Available commands in `KeyPressed` (`SingleFrameEditorApplication.h:280`): -- 'a' widget.FitContent() -- 'c' Crop tool -- 'm' Mask tool -- 'd' dump to json and diplay result (?) -- 'e' export current view to Dicom with dummy tags (?) -- 'i' wdiget.SwitchInvert -- 't' Move tool -- 'n' switch between nearest and bilinear interpolation -- 'r' Rotate tool -- 's' Resize tool -- 'w' Windowing tool -- 'ctrl+y' redo -- 'ctrl+z' undo diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/AppStatus.h --- a/Applications/Samples/SimpleViewer/AppStatus.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -#pragma once - -#include - - -namespace SimpleViewer -{ - struct AppStatus - { - std::string patientId; - std::string studyDescription; - std::string currentInstanceIdInMainViewport; - // note: if you add members here, update the serialization code below and deserialization in simple-viewer.ts -> onAppStatusUpdated() - - - AppStatus() - { - } - - void ToJson(Json::Value &output) const - { - output["patientId"] = patientId; - output["studyDescription"] = studyDescription; - output["currentInstanceIdInMainViewport"] = currentInstanceIdInMainViewport; - } - }; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp --- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "MainWidgetInteractor.h" - -#include "SimpleViewerApplication.h" - -namespace SimpleViewer { - - Deprecated::IWorldSceneMouseTracker* MainWidgetInteractor::CreateMouseTracker(Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - MouseButton button, - KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - Deprecated::IStatusBar* statusBar, - const std::vector& displayTouches) - { - if (button == MouseButton_Left) - { - if (application_.GetCurrentTool() == Tool_LineMeasure) - { - return new Deprecated::LineMeasureTracker(statusBar, dynamic_cast(widget).GetSlice(), - x, y, 255, 0, 0, application_.GetFont()); - } - else if (application_.GetCurrentTool() == Tool_CircleMeasure) - { - return new Deprecated::CircleMeasureTracker(statusBar, dynamic_cast(widget).GetSlice(), - x, y, 255, 0, 0, application_.GetFont()); - } - else if (application_.GetCurrentTool() == Tool_Crop) - { - // TODO - } - else if (application_.GetCurrentTool() == Tool_Windowing) - { - // TODO - } - else if (application_.GetCurrentTool() == Tool_Zoom) - { - // TODO - } - else if (application_.GetCurrentTool() == Tool_Pan) - { - // TODO - } - } - return NULL; - } - - void MainWidgetInteractor::MouseOver(CairoContext& context, - Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - double x, - double y, - Deprecated::IStatusBar* statusBar) - { - if (statusBar != NULL) - { - Vector p = dynamic_cast(widget).GetSlice().MapSliceToWorldCoordinates(x, y); - - char buf[64]; - sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", - p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); - statusBar->SetMessage(buf); - } - } - - void MainWidgetInteractor::MouseWheel(Deprecated::WorldSceneWidget& widget, - MouseWheelDirection direction, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - { - } - - void MainWidgetInteractor::KeyPressed(Deprecated::WorldSceneWidget& widget, - KeyboardKeys key, - char keyChar, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - { - switch (keyChar) - { - case 's': - widget.FitContent(); - break; - - default: - break; - } - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/MainWidgetInteractor.h --- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#include "../../../Framework/Deprecated/Widgets/IWorldSceneInteractor.h" - -using namespace OrthancStone; - -namespace SimpleViewer { - - class SimpleViewerApplication; - - class MainWidgetInteractor : public Deprecated::IWorldSceneInteractor - { - private: - SimpleViewerApplication& application_; - - public: - MainWidgetInteractor(SimpleViewerApplication& application) : - application_(application) - { - } - - /** - WorldSceneWidget: - */ - virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - MouseButton button, - KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - Deprecated::IStatusBar* statusBar, - const std::vector& displayTouches); - - virtual void MouseOver(CairoContext& context, - Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - double x, - double y, - Deprecated::IStatusBar* statusBar); - - virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, - MouseWheelDirection direction, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar); - - virtual void KeyPressed(Deprecated::WorldSceneWidget& widget, - KeyboardKeys key, - char keyChar, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar); - }; - - -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp --- a/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "SimpleViewerMainWindow.h" - -/** - * Don't use "ui_MainWindow.h" instead of below, as - * this makes CMake unable to detect when the UI file changes. - **/ -#include -#include "../SimpleViewerApplication.h" - - -namespace SimpleViewer -{ - template - bool ExecuteCommand(U* handler, const T& command) - { - std::string serializedCommand = StoneSerialize(command); - StoneDispatchToHandler(serializedCommand, handler); - } - - SimpleViewerMainWindow::SimpleViewerMainWindow( - OrthancStone::NativeStoneApplicationContext& context, - SimpleViewerApplication& stoneApplication, - QWidget *parent) : - QStoneMainWindow(context, parent), - ui_(new Ui::SimpleViewerMainWindow), - stoneApplication_(stoneApplication) - { - ui_->setupUi(this); - SetCentralStoneWidget(*ui_->cairoCentralWidget); - -#if QT_VERSION >= 0x050000 - connect(ui_->toolButtonCrop, &QToolButton::clicked, this, &SimpleViewerMainWindow::cropClicked); - connect(ui_->pushButtonUndoCrop, &QToolButton::clicked, this, &SimpleViewerMainWindow::undoCropClicked); - connect(ui_->toolButtonLine, &QToolButton::clicked, this, &SimpleViewerMainWindow::lineClicked); - connect(ui_->toolButtonCircle, &QToolButton::clicked, this, &SimpleViewerMainWindow::circleClicked); - connect(ui_->toolButtonWindowing, &QToolButton::clicked, this, &SimpleViewerMainWindow::windowingClicked); - connect(ui_->pushButtonRotate, &QPushButton::clicked, this, &SimpleViewerMainWindow::rotateClicked); - connect(ui_->pushButtonInvert, &QPushButton::clicked, this, &SimpleViewerMainWindow::invertClicked); -#else - connect(ui_->toolButtonCrop, SIGNAL(clicked()), this, SLOT(cropClicked())); - connect(ui_->toolButtonLine, SIGNAL(clicked()), this, SLOT(lineClicked())); - connect(ui_->toolButtonCircle, SIGNAL(clicked()), this, SLOT(circleClicked())); - connect(ui_->toolButtonWindowing, SIGNAL(clicked()), this, SLOT(windowingClicked())); - connect(ui_->pushButtonUndoCrop, SIGNAL(clicked()), this, SLOT(undoCropClicked())); - connect(ui_->pushButtonRotate, SIGNAL(clicked()), this, SLOT(rotateClicked())); - connect(ui_->pushButtonInvert, SIGNAL(clicked()), this, SLOT(invertClicked())); -#endif - } - - SimpleViewerMainWindow::~SimpleViewerMainWindow() - { - delete ui_; - } - - void SimpleViewerMainWindow::cropClicked() - { - stoneApplication_.ExecuteCommand(SelectTool(Tool_Crop)); - } - - void SimpleViewerMainWindow::undoCropClicked() - { - stoneApplication_.ExecuteCommand(Action(ActionType_UndoCrop)); - } - - void SimpleViewerMainWindow::lineClicked() - { - stoneApplication_.ExecuteCommand(SelectTool(Tool_LineMeasure)); - } - - void SimpleViewerMainWindow::circleClicked() - { - stoneApplication_.ExecuteCommand(SelectTool(Tool_CircleMeasure)); - } - - void SimpleViewerMainWindow::windowingClicked() - { - stoneApplication_.ExecuteCommand(SelectTool(Tool_Windowing)); - } - - void SimpleViewerMainWindow::rotateClicked() - { - stoneApplication_.ExecuteCommand(Action(ActionType_Rotate)); - } - - void SimpleViewerMainWindow::invertClicked() - { - stoneApplication_.ExecuteCommand(Action(ActionType_Invert)); - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.h --- a/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ -#pragma once - -#include -#include - -namespace Ui -{ - class SimpleViewerMainWindow; -} - -using namespace OrthancStone; - -namespace SimpleViewer -{ - class SimpleViewerApplication; - - class SimpleViewerMainWindow : public QStoneMainWindow - { - Q_OBJECT - - private: - Ui::SimpleViewerMainWindow* ui_; - SimpleViewerApplication& stoneApplication_; - - public: - explicit SimpleViewerMainWindow(OrthancStone::NativeStoneApplicationContext& context, SimpleViewerApplication& stoneApplication, QWidget *parent = 0); - ~SimpleViewerMainWindow(); - - private slots: - void cropClicked(); - void undoCropClicked(); - void rotateClicked(); - void windowingClicked(); - void lineClicked(); - void circleClicked(); - void invertClicked(); - }; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui --- a/Applications/Samples/SimpleViewer/Qt/SimpleViewerMainWindow.ui Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,151 +0,0 @@ - - - SimpleViewerMainWindow - - - - 0 - 0 - 903 - 634 - - - - - 500 - 300 - - - - - 500 - 300 - - - - Stone of Orthanc - - - Qt::LeftToRight - - - - - 0 - 0 - - - - Qt::LeftToRight - - - - QLayout::SetDefaultConstraint - - - - - - 0 - 500 - - - - - - - - - 0 - 100 - - - - - 16777215 - 100 - - - - - - - windowing - - - - - - - crop - - - - - - - undo crop - - - - - - - line - - - - - - - circle - - - - - - - rotate - - - - - - - invert - - - - - - - - - - - - 0 - 0 - 903 - 22 - - - - - Test - - - - - - - - - QCairoWidget - QGraphicsView -
QCairoWidget.h
-
-
- - -
diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Qt/mainQt.cpp --- a/Applications/Samples/SimpleViewer/Qt/mainQt.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -#include "Applications/Qt/QtStoneApplicationRunner.h" - -#include "../SimpleViewerApplication.h" -#include "Framework/Messages/MessageBroker.h" - - -int main(int argc, char* argv[]) -{ - OrthancStone::MessageBroker broker; - SimpleViewer::SimpleViewerApplication stoneApplication(broker); - - OrthancStone::QtStoneApplicationRunner qtAppRunner(broker, stoneApplication); - return qtAppRunner.Execute(argc, argv); -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp --- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,225 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#include "SimpleViewerApplication.h" - -#if ORTHANC_ENABLE_QT == 1 -# include "Qt/SimpleViewerMainWindow.h" -#endif - -#if ORTHANC_ENABLE_WASM == 1 -# include -#endif - -namespace SimpleViewer -{ - - void SimpleViewerApplication::Initialize(StoneApplicationContext* context, - Deprecated::IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - context_ = context; - statusBar_ = &statusBar; - - {// initialize viewports and layout - mainLayout_ = new Deprecated::LayoutWidget("main-layout"); - mainLayout_->SetPadding(10); - mainLayout_->SetBackgroundCleared(true); - mainLayout_->SetBackgroundColor(0, 0, 0); - mainLayout_->SetHorizontal(); - - thumbnailsLayout_ = new Deprecated::LayoutWidget("thumbnail-layout"); - thumbnailsLayout_->SetPadding(10); - thumbnailsLayout_->SetBackgroundCleared(true); - thumbnailsLayout_->SetBackgroundColor(50, 50, 50); - thumbnailsLayout_->SetVertical(); - - mainWidget_ = new Deprecated::SliceViewerWidget(IObserver::GetBroker(), "main-viewport"); - //mainWidget_->RegisterObserver(*this); - - // hierarchy - mainLayout_->AddWidget(thumbnailsLayout_); - mainLayout_->AddWidget(mainWidget_); - - // sources - smartLoader_.reset(new Deprecated::SmartLoader(IObserver::GetBroker(), context->GetOrthancApiClient())); - smartLoader_->SetImageQuality(Deprecated::SliceImageQuality_FullPam); - - mainLayout_->SetTransmitMouseOver(true); - mainWidgetInteractor_.reset(new MainWidgetInteractor(*this)); - mainWidget_->SetInteractor(*mainWidgetInteractor_); - thumbnailInteractor_.reset(new ThumbnailInteractor(*this)); - } - - statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); - statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport"); - - - if (parameters.count("studyId") < 1) - { - LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc"; - context->GetOrthancApiClient().GetJsonAsync("/studies", new Callable(*this, &SimpleViewerApplication::OnStudyListReceived)); - } - else - { - SelectStudy(parameters["studyId"].as()); - } - } - - - void SimpleViewerApplication::DeclareStartupOptions(boost::program_options::options_description& options) - { - boost::program_options::options_description generic("Sample options"); - generic.add_options() - ("studyId", boost::program_options::value(), - "Orthanc ID of the study") - ; - - options.add(generic); - } - - void SimpleViewerApplication::OnStudyListReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) - { - const Json::Value& response = message.GetJson(); - - if (response.isArray() && - response.size() >= 1) - { - SelectStudy(response[0].asString()); - } - } - void SimpleViewerApplication::OnStudyReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) - { - const Json::Value& response = message.GetJson(); - - if (response.isObject() && response["Series"].isArray()) - { - for (size_t i=0; i < response["Series"].size(); i++) - { - context_->GetOrthancApiClient().GetJsonAsync("/series/" + response["Series"][(int)i].asString(), new Callable(*this, &SimpleViewerApplication::OnSeriesReceived)); - } - } - } - - void SimpleViewerApplication::OnSeriesReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) - { - const Json::Value& response = message.GetJson(); - - if (response.isObject() && - response["Instances"].isArray() && - response["Instances"].size() > 0) - { - // keep track of all instances IDs - const std::string& seriesId = response["ID"].asString(); - seriesTags_[seriesId] = response; - instancesIdsPerSeriesId_[seriesId] = std::vector(); - for (size_t i = 0; i < response["Instances"].size(); i++) - { - const std::string& instanceId = response["Instances"][static_cast(i)].asString(); - instancesIdsPerSeriesId_[seriesId].push_back(instanceId); - } - - // load the first instance in the thumbnail - LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]); - - // if this is the first thumbnail loaded, load the first instance in the mainWidget - if (mainWidget_->GetLayerCount() == 0) - { - smartLoader_->SetFrameInWidget(*mainWidget_, 0, instancesIdsPerSeriesId_[seriesId][0], 0); - } - } - } - - void SimpleViewerApplication::LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId) - { - LOG(INFO) << "Loading thumbnail for series " << seriesId; - - Deprecated::SliceViewerWidget* thumbnailWidget = - new Deprecated::SliceViewerWidget(IObserver::GetBroker(), "thumbnail-series-" + seriesId); - thumbnails_.push_back(thumbnailWidget); - thumbnailsLayout_->AddWidget(thumbnailWidget); - - thumbnailWidget->RegisterObserverCallback( - new Callable - (*this, &SimpleViewerApplication::OnWidgetGeometryChanged)); - - smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0); - thumbnailWidget->SetInteractor(*thumbnailInteractor_); - } - - void SimpleViewerApplication::SelectStudy(const std::string& studyId) - { - context_->GetOrthancApiClient().GetJsonAsync("/studies/" + studyId, new Callable(*this, &SimpleViewerApplication::OnStudyReceived)); - } - - void SimpleViewerApplication::OnWidgetGeometryChanged(const Deprecated::SliceViewerWidget::GeometryChangedMessage& message) - { - // TODO: The "const_cast" could probably be replaced by "mainWidget_" - const_cast(message.GetOrigin()).FitContent(); - } - - void SimpleViewerApplication::SelectSeriesInMainViewport(const std::string& seriesId) - { - smartLoader_->SetFrameInWidget(*mainWidget_, 0, instancesIdsPerSeriesId_[seriesId][0], 0); - } - - bool SimpleViewerApplication::Handle(const StoneSampleCommands::SelectTool& value) - { - currentTool_ = value.tool; - return true; - } - - bool SimpleViewerApplication::Handle(const StoneSampleCommands::Action& value) - { - switch (value.type) - { - case ActionType_Invert: - // TODO - break; - case ActionType_UndoCrop: - // TODO - break; - case ActionType_Rotate: - // TODO - break; - default: - throw std::runtime_error("Action type not supported"); - } - return true; - } - -#if ORTHANC_ENABLE_QT==1 - QStoneMainWindow* SimpleViewerApplication::CreateQtMainWindow() - { - return new SimpleViewerMainWindow(dynamic_cast(*context_), *this); - } -#endif - -#if ORTHANC_ENABLE_WASM==1 - void SimpleViewerApplication::InitializeWasm() { - - AttachWidgetToWasmViewport("canvasThumbnails", thumbnailsLayout_); - AttachWidgetToWasmViewport("canvasMain", mainWidget_); - } -#endif - - -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/SimpleViewerApplication.h --- a/Applications/Samples/SimpleViewer/SimpleViewerApplication.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - - /* - This header contains the command definitions for the sample applications - */ -#include "Applications/Samples/StoneSampleCommands_generated.hpp" -using namespace StoneSampleCommands; - -#include "Applications/IStoneApplication.h" - -#include "../../../Framework/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" -#include "Platforms/Wasm/Defaults.h" -#endif - -#if ORTHANC_ENABLE_QT==1 -#include "Qt/SimpleViewerMainWindow.h" -#endif - -#include -#include - -#include "ThumbnailInteractor.h" -#include "MainWidgetInteractor.h" -#include "AppStatus.h" - -using namespace OrthancStone; - - -namespace SimpleViewer -{ - - class SimpleViewerApplication - : public IStoneApplication - , public IObserver - , public IObservable - , public StoneSampleCommands::IHandler - { - public: - - struct StatusUpdatedMessage : public IMessage - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - const AppStatus& status_; - - StatusUpdatedMessage(const AppStatus& status) - : status_(status) - { - } - }; - - private: - Tool currentTool_; - - std::unique_ptr mainWidgetInteractor_; - std::unique_ptr thumbnailInteractor_; - Deprecated::LayoutWidget* mainLayout_; - Deprecated::LayoutWidget* thumbnailsLayout_; - Deprecated::SliceViewerWidget* mainWidget_; - std::vector thumbnails_; - std::map > instancesIdsPerSeriesId_; - std::map seriesTags_; - unsigned int currentInstanceIndex_; - Deprecated::WidgetViewport* wasmViewport1_; - Deprecated::WidgetViewport* wasmViewport2_; - - Deprecated::IStatusBar* statusBar_; - std::unique_ptr smartLoader_; - - Orthanc::Font font_; - - public: - SimpleViewerApplication(MessageBroker& broker) : - IObserver(broker), - IObservable(broker), - currentTool_(StoneSampleCommands::Tool_LineMeasure), - mainLayout_(NULL), - currentInstanceIndex_(0), - wasmViewport1_(NULL), - wasmViewport2_(NULL) - { - font_.LoadFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); - } - - virtual void Finalize() ORTHANC_OVERRIDE {} - virtual Deprecated::IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainLayout_;} - - virtual void DeclareStartupOptions(boost::program_options::options_description& options) ORTHANC_OVERRIDE; - virtual void Initialize(StoneApplicationContext* context, - Deprecated::IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE; - - void OnStudyListReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message); - - void OnStudyReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message); - - void OnSeriesReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message); - - void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId); - - void SelectStudy(const std::string& studyId); - - void OnWidgetGeometryChanged(const Deprecated::SliceViewerWidget::GeometryChangedMessage& message); - - void SelectSeriesInMainViewport(const std::string& seriesId); - - - Tool GetCurrentTool() const - { - return currentTool_; - } - - const Orthanc::Font& GetFont() const - { - return font_; - } - - // ExecuteAction method was empty (its body was a single "TODO" comment) - virtual bool Handle(const SelectTool& value) ORTHANC_OVERRIDE; - virtual bool Handle(const Action& value) ORTHANC_OVERRIDE; - - template - bool ExecuteCommand(const T& cmd) - { - std::string cmdStr = StoneSampleCommands::StoneSerialize(cmd); - return StoneSampleCommands::StoneDispatchToHandler(cmdStr, this); - } - - virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE - { - StoneSampleCommands::StoneDispatchToHandler(data, this); - } - - virtual std::string GetTitle() const ORTHANC_OVERRIDE {return "SimpleViewer";} - -#if ORTHANC_ENABLE_WASM==1 - virtual void InitializeWasm() ORTHANC_OVERRIDE; -#endif - -#if ORTHANC_ENABLE_QT==1 - virtual QStoneMainWindow* CreateQtMainWindow(); -#endif - }; - - -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp --- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "ThumbnailInteractor.h" - -#include "SimpleViewerApplication.h" - -namespace SimpleViewer { - - Deprecated::IWorldSceneMouseTracker* ThumbnailInteractor::CreateMouseTracker(Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - MouseButton button, - KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - Deprecated::IStatusBar* statusBar, - const std::vector& displayTouches) - { - if (button == MouseButton_Left) - { - statusBar->SetMessage("selected thumbnail " + widget.GetName()); - std::string seriesId = widget.GetName().substr(strlen("thumbnail-series-")); - application_.SelectSeriesInMainViewport(seriesId); - } - return NULL; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/ThumbnailInteractor.h --- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../../../Framework/Deprecated/Widgets/IWorldSceneInteractor.h" - -using namespace OrthancStone; - -namespace SimpleViewer { - - class SimpleViewerApplication; - - class ThumbnailInteractor : public Deprecated::IWorldSceneInteractor - { - private: - SimpleViewerApplication& application_; - public: - ThumbnailInteractor(SimpleViewerApplication& application) : - application_(application) - { - } - - virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - MouseButton button, - KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - Deprecated::IStatusBar* statusBar, - const std::vector& displayTouches); - - virtual void MouseOver(CairoContext& context, - Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - double x, - double y, - Deprecated::IStatusBar* statusBar) - {} - - virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, - MouseWheelDirection direction, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - {} - - virtual void KeyPressed(Deprecated::WorldSceneWidget& widget, - KeyboardKeys key, - char keyChar, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - {} - - }; - - -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp --- a/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "SimpleViewerWasmApplicationAdapter.h" - -namespace SimpleViewer -{ - - SimpleViewerWasmApplicationAdapter::SimpleViewerWasmApplicationAdapter(MessageBroker &broker, SimpleViewerApplication &application) - : WasmPlatformApplicationAdapter(broker, application), - viewerApplication_(application) - { - application.RegisterObserverCallback(new Callable(*this, &SimpleViewerWasmApplicationAdapter::OnStatusUpdated)); - } - - void SimpleViewerWasmApplicationAdapter::OnStatusUpdated(const SimpleViewerApplication::StatusUpdatedMessage &message) - { - Json::Value statusJson; - message.status_.ToJson(statusJson); - - Json::Value event; - event["event"] = "appStatusUpdated"; - event["data"] = statusJson; - - Json::StreamWriterBuilder builder; - std::unique_ptr writer(builder.newStreamWriter()); - std::ostringstream outputStr; - - writer->write(event, &outputStr); - - NotifyStatusUpdateFromCppToWebWithString(outputStr.str()); - } - -} // namespace SimpleViewer \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.h --- a/Applications/Samples/SimpleViewer/Wasm/SimpleViewerWasmApplicationAdapter.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#include -#include -#include - -#include "../SimpleViewerApplication.h" - -namespace SimpleViewer { - - class SimpleViewerWasmApplicationAdapter : public WasmPlatformApplicationAdapter - { - SimpleViewerApplication& viewerApplication_; - - public: - SimpleViewerWasmApplicationAdapter(MessageBroker& broker, SimpleViewerApplication& application); - - private: - void OnStatusUpdated(const SimpleViewerApplication::StatusUpdatedMessage& message); - - }; - -} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp --- a/Applications/Samples/SimpleViewer/Wasm/mainWasm.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "Platforms/Wasm/WasmWebService.h" -#include "Platforms/Wasm/WasmViewport.h" - -#include - -#include "../SimpleViewerApplication.h" -#include "SimpleViewerWasmApplicationAdapter.h" - - -OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) { - - return new SimpleViewer::SimpleViewerApplication(broker); -} - -OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, IStoneApplication* application) -{ - return new SimpleViewer::SimpleViewerWasmApplicationAdapter(broker, *(dynamic_cast(application))); -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Wasm/simple-viewer.html --- a/Applications/Samples/SimpleViewer/Wasm/simple-viewer.html Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ - - - - - - - - - - - - Simple Viewer - - - - -
-
- -
-
- -
-
-
- - - - - - - - - -
- - - - \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Wasm/simple-viewer.ts --- a/Applications/Samples/SimpleViewer/Wasm/simple-viewer.ts Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -import wasmApplicationRunner = require('../../../../Platforms/Wasm/wasm-application-runner'); - -wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSimpleViewer", "/orthanc"); - -function SelectTool(toolName: string) { - var command = { - command: "selectTool:" + toolName, - commandType: "generic-no-arg-command", - args: { - } - }; - wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); -} - -function PerformAction(actionName: string) { - var command = { - command: "action:" + actionName, - commandType: "generic-no-arg-command", - args: { - } - }; - wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); -} - -class SimpleViewerUI { - - private _labelPatientId: HTMLSpanElement; - private _labelStudyDescription: HTMLSpanElement; - - public constructor() { - // install "SelectTool" handlers - document.querySelectorAll("[tool-selector]").forEach((e) => { - (e as HTMLButtonElement).addEventListener("click", () => { - SelectTool(e.attributes["tool-selector"].value); - }); - }); - - // install "PerformAction" handlers - document.querySelectorAll("[action-trigger]").forEach((e) => { - (e as HTMLButtonElement).addEventListener("click", () => { - PerformAction(e.attributes["action-trigger"].value); - }); - }); - - // connect all ui elements to members - this._labelPatientId = document.getElementById("label-patient-id") as HTMLSpanElement; - this._labelStudyDescription = document.getElementById("label-study-description") as HTMLSpanElement; - } - - public onAppStatusUpdated(status: any) { - this._labelPatientId.innerText = status["patientId"]; - this._labelStudyDescription.innerText = status["studyDescription"]; - // this.highlighThumbnail(status["currentInstanceIdInMainViewport"]); - } - -} - -var ui = new SimpleViewerUI(); - -// this method is called "from the C++ code" when the StoneApplication is updated. -// it can be used to update the UI of the application -function UpdateWebApplicationWithString(statusUpdateMessageString: string) { - console.log("updating web application with string: ", statusUpdateMessageString); - let statusUpdateMessage = JSON.parse(statusUpdateMessageString); - - if ("event" in statusUpdateMessage) { - let eventName = statusUpdateMessage["event"]; - if (eventName == "appStatusUpdated") { - ui.onAppStatusUpdated(statusUpdateMessage["data"]); - } - } -} - -function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) { - console.log("updating web application with serialized message: ", statusUpdateMessageString); - console.log(""); -} - -// make it available to other js scripts in the application -( window).UpdateWebApplicationWithString = UpdateWebApplicationWithString; -( window).UpdateWebApplicationWithSerializedMessage = UpdateWebApplicationWithSerializedMessage; diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Wasm/styles.css --- a/Applications/Samples/SimpleViewer/Wasm/styles.css Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -html, body { - width: 100%; - height: 100%; - margin: 0px; - border: 0; - overflow: hidden; /* Disable scrollbars */ - display: block; /* No floating content on sides */ - background-color: black; - color: white; - font-family: Arial, Helvetica, sans-serif; -} - -canvas { - left:0px; - top:0px; -} - -#canvas-group { - padding:5px; - background-color: grey; -} - -#status-group { - padding:5px; -} - -#worklist-group { - padding:5px; -} - -.vsol-button { - height: 40px; -} - -#thumbnails-group ul li { - display: inline; - list-style: none; -} - -.thumbnail { - width: 100px; - height: 100px; - padding: 3px; -} - -.thumbnail-selected { - border-width: 1px; - border-color: red; - border-style: solid; -} - -#template-thumbnail-li { - display: none !important; -} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewer/Wasm/tsconfig-simple-viewer.json --- a/Applications/Samples/SimpleViewer/Wasm/tsconfig-simple-viewer.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -{ - "extends" : "../../Web/tsconfig-samples", - "compilerOptions": { - }, - "include" : [ - "simple-viewer.ts", - "../../build-wasm/ApplicationCommands_generated.ts" - ] -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SimpleViewerApplicationSingleFile.h --- a/Applications/Samples/SimpleViewerApplicationSingleFile.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,460 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "SampleApplicationBase.h" - -#include "../../Framework/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" -#include "../../Platforms/Wasm/Defaults.h" -#endif - -#include -#include - -namespace OrthancStone -{ - namespace Samples - { - class SimpleViewerApplication : - public SampleSingleCanvasWithButtonsApplicationBase, - public IObserver - { - private: - class ThumbnailInteractor : public Deprecated::IWorldSceneInteractor - { - private: - SimpleViewerApplication& application_; - - public: - ThumbnailInteractor(SimpleViewerApplication& application) : - application_(application) - { - } - - virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - MouseButton button, - KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - Deprecated::IStatusBar* statusBar, - const std::vector& displayTouches) - { - if (button == MouseButton_Left) - { - statusBar->SetMessage("selected thumbnail " + widget.GetName()); - std::string seriesId = widget.GetName().substr(strlen("thumbnail-series-")); - application_.SelectSeriesInMainViewport(seriesId); - } - return NULL; - } - - virtual void MouseOver(CairoContext& context, - Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - double x, - double y, - Deprecated::IStatusBar* statusBar) - { - } - - virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, - MouseWheelDirection direction, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - { - } - - virtual void KeyPressed(Deprecated::WorldSceneWidget& widget, - KeyboardKeys key, - char keyChar, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - { - } - }; - - class MainWidgetInteractor : public Deprecated::IWorldSceneInteractor - { - private: - SimpleViewerApplication& application_; - - public: - MainWidgetInteractor(SimpleViewerApplication& application) : - application_(application) - { - } - - virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - MouseButton button, - KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - Deprecated::IStatusBar* statusBar, - const std::vector& displayTouches) - { - if (button == MouseButton_Left) - { - if (application_.currentTool_ == Tool_LineMeasure) - { - return new Deprecated::LineMeasureTracker(statusBar, dynamic_cast(widget).GetSlice(), - x, y, 255, 0, 0, application_.GetFont()); - } - else if (application_.currentTool_ == Tool_CircleMeasure) - { - return new Deprecated::CircleMeasureTracker(statusBar, dynamic_cast(widget).GetSlice(), - x, y, 255, 0, 0, application_.GetFont()); - } - } - return NULL; - } - - virtual void MouseOver(CairoContext& context, - Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - double x, - double y, - Deprecated::IStatusBar* statusBar) - { - if (statusBar != NULL) - { - Vector p = dynamic_cast(widget).GetSlice().MapSliceToWorldCoordinates(x, y); - - char buf[64]; - sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", - p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); - statusBar->SetMessage(buf); - } - } - - virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, - MouseWheelDirection direction, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - { - } - - virtual void KeyPressed(Deprecated::WorldSceneWidget& widget, - KeyboardKeys key, - char keyChar, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - { - switch (keyChar) - { - case 's': - widget.FitContent(); - break; - - case 'l': - application_.currentTool_ = Tool_LineMeasure; - break; - - case 'c': - application_.currentTool_ = Tool_CircleMeasure; - break; - - default: - break; - } - } - }; - - -#if ORTHANC_ENABLE_WASM==1 - class SimpleViewerApplicationAdapter : public WasmPlatformApplicationAdapter - { - SimpleViewerApplication& viewerApplication_; - - public: - SimpleViewerApplicationAdapter(MessageBroker& broker, SimpleViewerApplication& application) - : WasmPlatformApplicationAdapter(broker, application), - viewerApplication_(application) - { - } - - virtual void HandleSerializedMessageFromWeb(std::string& output, const std::string& input) - { - if (input == "select-tool:line-measure") - { - viewerApplication_.currentTool_ = Tool_LineMeasure; - NotifyStatusUpdateFromCppToWebWithString("currentTool=line-measure"); - } - else if (input == "select-tool:circle-measure") - { - viewerApplication_.currentTool_ = Tool_CircleMeasure; - NotifyStatusUpdateFromCppToWebWithString("currentTool=circle-measure"); - } - - output = "ok"; - } - - virtual void NotifySerializedMessageFromCppToWeb(const std::string& statusUpdateMessage) - { - UpdateStoneApplicationStatusFromCppWithSerializedMessage(statusUpdateMessage.c_str()); - } - - virtual void NotifyStatusUpdateFromCppToWebWithString(const std::string& statusUpdateMessage) - { - UpdateStoneApplicationStatusFromCppWithString(statusUpdateMessage.c_str()); - } - - }; -#endif - enum Tool { - Tool_LineMeasure, - Tool_CircleMeasure - }; - - Tool currentTool_; - std::unique_ptr mainWidgetInteractor_; - std::unique_ptr thumbnailInteractor_; - Deprecated::LayoutWidget* mainLayout_; - Deprecated::LayoutWidget* thumbnailsLayout_; - std::vector thumbnails_; - - std::map > instancesIdsPerSeriesId_; - std::map seriesTags_; - - unsigned int currentInstanceIndex_; - Deprecated::WidgetViewport* wasmViewport1_; - Deprecated::WidgetViewport* wasmViewport2_; - - Deprecated::IStatusBar* statusBar_; - std::unique_ptr smartLoader_; - - Orthanc::Font font_; - - public: - SimpleViewerApplication(MessageBroker& broker) : - IObserver(broker), - currentTool_(Tool_LineMeasure), - mainLayout_(NULL), - currentInstanceIndex_(0), - wasmViewport1_(NULL), - wasmViewport2_(NULL) - { - font_.LoadFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); -// DeclareIgnoredMessage(MessageType_Widget_ContentChanged); - } - - virtual void DeclareStartupOptions(boost::program_options::options_description& options) - { - boost::program_options::options_description generic("Sample options"); - generic.add_options() - ("studyId", boost::program_options::value(), - "Orthanc ID of the study") - ; - - options.add(generic); - } - - virtual void Initialize(StoneApplicationContext* context, - Deprecated::IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - using namespace OrthancStone; - - context_ = context; - statusBar_ = &statusBar; - - {// initialize viewports and layout - mainLayout_ = new Deprecated::LayoutWidget("main-layout"); - mainLayout_->SetPadding(10); - mainLayout_->SetBackgroundCleared(true); - mainLayout_->SetBackgroundColor(0, 0, 0); - mainLayout_->SetHorizontal(); - - thumbnailsLayout_ = new Deprecated::LayoutWidget("thumbnail-layout"); - thumbnailsLayout_->SetPadding(10); - thumbnailsLayout_->SetBackgroundCleared(true); - thumbnailsLayout_->SetBackgroundColor(50, 50, 50); - thumbnailsLayout_->SetVertical(); - - mainWidget_ = new Deprecated::SliceViewerWidget(GetBroker(), "main-viewport"); - //mainWidget_->RegisterObserver(*this); - - // hierarchy - mainLayout_->AddWidget(thumbnailsLayout_); - mainLayout_->AddWidget(mainWidget_); - - // sources - smartLoader_.reset(new Deprecated::SmartLoader(GetBroker(), context->GetOrthancApiClient())); - smartLoader_->SetImageQuality(Deprecated::SliceImageQuality_FullPam); - - mainLayout_->SetTransmitMouseOver(true); - mainWidgetInteractor_.reset(new MainWidgetInteractor(*this)); - mainWidget_->SetInteractor(*mainWidgetInteractor_); - thumbnailInteractor_.reset(new ThumbnailInteractor(*this)); - } - - statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); - statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport"); - - - if (parameters.count("studyId") < 1) - { - LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc"; - context->GetOrthancApiClient().GetJsonAsync( - "/studies", - new Callable - (*this, &SimpleViewerApplication::OnStudyListReceived)); - } - else - { - SelectStudy(parameters["studyId"].as()); - } - } - - void OnStudyListReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) - { - const Json::Value& response = message.GetJson(); - - if (response.isArray() && - response.size() >= 1) - { - SelectStudy(response[0].asString()); - } - } - - void OnStudyReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) - { - const Json::Value& response = message.GetJson(); - - if (response.isObject() && response["Series"].isArray()) - { - for (size_t i=0; i < response["Series"].size(); i++) - { - context_->GetOrthancApiClient().GetJsonAsync( - "/series/" + response["Series"][(int)i].asString(), - new Callable - (*this, &SimpleViewerApplication::OnSeriesReceived)); - } - } - } - - void OnSeriesReceived(const Deprecated::OrthancApiClient::JsonResponseReadyMessage& message) - { - const Json::Value& response = message.GetJson(); - - if (response.isObject() && - response["Instances"].isArray() && - response["Instances"].size() > 0) - { - // keep track of all instances IDs - const std::string& seriesId = response["ID"].asString(); - seriesTags_[seriesId] = response; - instancesIdsPerSeriesId_[seriesId] = std::vector(); - for (size_t i = 0; i < response["Instances"].size(); i++) - { - const std::string& instanceId = response["Instances"][static_cast(i)].asString(); - instancesIdsPerSeriesId_[seriesId].push_back(instanceId); - } - - // load the first instance in the thumbnail - LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]); - - // if this is the first thumbnail loaded, load the first instance in the mainWidget - Deprecated::SliceViewerWidget& widget = *dynamic_cast(mainWidget_); - if (widget.GetLayerCount() == 0) - { - smartLoader_->SetFrameInWidget(widget, 0, instancesIdsPerSeriesId_[seriesId][0], 0); - } - } - } - - void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId) - { - LOG(INFO) << "Loading thumbnail for series " << seriesId; - Deprecated::SliceViewerWidget* thumbnailWidget = new Deprecated::SliceViewerWidget(GetBroker(), "thumbnail-series-" + seriesId); - thumbnails_.push_back(thumbnailWidget); - thumbnailsLayout_->AddWidget(thumbnailWidget); - thumbnailWidget->RegisterObserverCallback(new Callable(*this, &SimpleViewerApplication::OnWidgetGeometryChanged)); - smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0); - thumbnailWidget->SetInteractor(*thumbnailInteractor_); - } - - void SelectStudy(const std::string& studyId) - { - LOG(INFO) << "Selecting study: " << studyId; - context_->GetOrthancApiClient().GetJsonAsync( - "/studies/" + studyId, new Callable - (*this, &SimpleViewerApplication::OnStudyReceived)); - } - - void OnWidgetGeometryChanged(const Deprecated::SliceViewerWidget::GeometryChangedMessage& message) - { - // TODO: The "const_cast" could probably be replaced by "mainWidget" - const_cast(message.GetOrigin()).FitContent(); - } - - void SelectSeriesInMainViewport(const std::string& seriesId) - { - Deprecated::SliceViewerWidget& widget = *dynamic_cast(mainWidget_); - smartLoader_->SetFrameInWidget(widget, 0, instancesIdsPerSeriesId_[seriesId][0], 0); - } - - const Orthanc::Font& GetFont() const - { - return font_; - } - - virtual void OnPushButton1Clicked() {} - virtual void OnPushButton2Clicked() {} - virtual void OnTool1Clicked() { currentTool_ = Tool_LineMeasure;} - virtual void OnTool2Clicked() { currentTool_ = Tool_CircleMeasure;} - - virtual void GetButtonNames(std::string& pushButton1, - std::string& pushButton2, - std::string& tool1, - std::string& tool2) - { - tool1 = "line"; - tool2 = "circle"; - pushButton1 = "action1"; - pushButton2 = "action2"; - } - -#if ORTHANC_ENABLE_WASM==1 - virtual void InitializeWasm() - { - AttachWidgetToWasmViewport("canvas", thumbnailsLayout_); - AttachWidgetToWasmViewport("canvas2", mainWidget_); - } -#endif - - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SingleFrameApplication.h --- a/Applications/Samples/SingleFrameApplication.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,271 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "SampleApplicationBase.h" - -#include "../../Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h" -#include "../../Framework/Deprecated/Widgets/SliceViewerWidget.h" - -#include -#include - -#include - - -namespace OrthancStone -{ - namespace Samples - { - class SingleFrameApplication : - public SampleSingleCanvasApplicationBase, - public IObserver - { - private: - class Interactor : public Deprecated::IWorldSceneInteractor - { - private: - SingleFrameApplication& application_; - - public: - Interactor(SingleFrameApplication& application) : - application_(application) - { - } - - virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - MouseButton button, - KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - Deprecated::IStatusBar* statusBar, - const std::vector& displayTouches) - { - return NULL; - } - - virtual void MouseOver(CairoContext& context, - Deprecated::WorldSceneWidget& widget, - const Deprecated::ViewportGeometry& view, - double x, - double y, - Deprecated::IStatusBar* statusBar) - { - if (statusBar != NULL) - { - Vector p = dynamic_cast(widget).GetSlice().MapSliceToWorldCoordinates(x, y); - - char buf[64]; - sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", - p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); - statusBar->SetMessage(buf); - } - } - - virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, - MouseWheelDirection direction, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - { - int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1); - - switch (direction) - { - case MouseWheelDirection_Up: - application_.OffsetSlice(-scale); - break; - - case MouseWheelDirection_Down: - application_.OffsetSlice(scale); - break; - - default: - break; - } - } - - virtual void KeyPressed(Deprecated::WorldSceneWidget& widget, - KeyboardKeys key, - char keyChar, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - { - switch (keyChar) - { - case 's': - widget.FitContent(); - break; - - default: - break; - } - } - }; - - - void OffsetSlice(int offset) - { - if (source_ != NULL) - { - int slice = static_cast(slice_) + offset; - - if (slice < 0) - { - slice = 0; - } - - if (slice >= static_cast(source_->GetSlicesCount())) - { - slice = static_cast(source_->GetSlicesCount()) - 1; - } - - if (slice != static_cast(slice_)) - { - SetSlice(slice); - } - } - } - - - Deprecated::SliceViewerWidget& GetMainWidget() - { - return *dynamic_cast(mainWidget_); - } - - - void SetSlice(size_t index) - { - if (source_ != NULL && - index < source_->GetSlicesCount()) - { - slice_ = static_cast(index); - -#if 1 - GetMainWidget().SetSlice(source_->GetSlice(slice_).GetGeometry()); -#else - // TEST for scene extents - Rotate the axes - double a = 15.0 / 180.0 * boost::math::constants::pi(); - -#if 1 - Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); - Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0); -#else - // Flip the normal - Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); - Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0); -#endif - - SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y); - widget_->SetSlice(s); -#endif - } - } - - - void OnMainWidgetGeometryReady(const Deprecated::IVolumeSlicer::GeometryReadyMessage& message) - { - // Once the geometry of the series is downloaded from Orthanc, - // display its middle slice, and adapt the viewport to fit this - // slice - if (source_ == &message.GetOrigin()) - { - SetSlice(source_->GetSlicesCount() / 2); - } - - GetMainWidget().FitContent(); - } - - std::unique_ptr mainWidgetInteractor_; - const Deprecated::DicomSeriesVolumeSlicer* source_; - unsigned int slice_; - - public: - SingleFrameApplication(MessageBroker& broker) : - IObserver(broker), - source_(NULL), - slice_(0) - { - } - - virtual void DeclareStartupOptions(boost::program_options::options_description& options) - { - boost::program_options::options_description generic("Sample options"); - generic.add_options() - ("instance", boost::program_options::value(), - "Orthanc ID of the instance") - ("frame", boost::program_options::value()->default_value(0), - "Number of the frame, for multi-frame DICOM instances") - ("smooth", boost::program_options::value()->default_value(true), - "Enable bilinear interpolation to smooth the image") - ; - - options.add(generic); - } - - virtual void Initialize(StoneApplicationContext* context, - Deprecated::IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - using namespace OrthancStone; - - context_ = context; - - statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); - - if (parameters.count("instance") != 1) - { - LOG(ERROR) << "The instance ID is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - std::string instance = parameters["instance"].as(); - int frame = parameters["frame"].as(); - - mainWidget_ = new Deprecated::SliceViewerWidget(GetBroker(), "main-widget"); - - std::unique_ptr layer(new Deprecated::DicomSeriesVolumeSlicer(GetBroker(), context->GetOrthancApiClient())); - source_ = layer.get(); - layer->LoadFrame(instance, frame); - layer->RegisterObserverCallback(new Callable(*this, &SingleFrameApplication::OnMainWidgetGeometryReady)); - GetMainWidget().AddLayer(layer.release()); - - Deprecated::RenderStyle s; - - if (parameters["smooth"].as()) - { - s.interpolation_ = ImageInterpolation_Bilinear; - } - - GetMainWidget().SetLayerStyle(0, s); - GetMainWidget().SetTransmitMouseOver(true); - - mainWidgetInteractor_.reset(new Interactor(*this)); - GetMainWidget().SetInteractor(*mainWidgetInteractor_); - } - }; - - - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SingleFrameEditorApplication.h --- a/Applications/Samples/SingleFrameEditorApplication.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,539 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "SampleApplicationBase.h" - -#include "../../Framework/Radiography/RadiographyLayerCropTracker.h" -#include "../../Framework/Radiography/RadiographyLayerMaskTracker.h" -#include "../../Framework/Radiography/RadiographyLayerMoveTracker.h" -#include "../../Framework/Radiography/RadiographyLayerResizeTracker.h" -#include "../../Framework/Radiography/RadiographyLayerRotateTracker.h" -#include "../../Framework/Radiography/RadiographyScene.h" -#include "../../Framework/Radiography/RadiographySceneCommand.h" -#include "../../Framework/Radiography/RadiographyWidget.h" -#include "../../Framework/Radiography/RadiographyWindowingTracker.h" -#include "../../Framework/Radiography/RadiographySceneWriter.h" -#include "../../Framework/Radiography/RadiographySceneReader.h" -#include "../../Framework/Radiography/RadiographyMaskLayer.h" -#include "../../Framework/Toolbox/TextRenderer.h" - -#include -#include -#include -#include -#include - - -// Export using PAM is faster than using PNG, but requires Orthanc -// core >= 1.4.3 -#define EXPORT_USING_PAM 1 - - -namespace OrthancStone -{ - namespace Samples - { - class RadiographyEditorInteractor : - public Deprecated::IWorldSceneInteractor, - public IObserver - { - private: - enum Tool - { - Tool_Move, - Tool_Rotate, - Tool_Crop, - Tool_Resize, - Tool_Mask, - Tool_Windowing - }; - - - StoneApplicationContext* context_; - UndoRedoStack undoRedoStack_; - Tool tool_; - RadiographyMaskLayer* maskLayer_; - - - static double GetHandleSize() - { - return 10.0; - } - - - public: - RadiographyEditorInteractor(MessageBroker& broker) : - IObserver(broker), - context_(NULL), - tool_(Tool_Move), - maskLayer_(NULL) - { - } - - void SetContext(StoneApplicationContext& context) - { - context_ = &context; - } - - void SetMaskLayer(RadiographyMaskLayer* maskLayer) - { - maskLayer_ = maskLayer; - } - virtual Deprecated::IWorldSceneMouseTracker* CreateMouseTracker(Deprecated::WorldSceneWidget& worldWidget, - const Deprecated::ViewportGeometry& view, - MouseButton button, - KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - Deprecated::IStatusBar* statusBar, - const std::vector& displayTouches) - { - RadiographyWidget& widget = dynamic_cast(worldWidget); - - if (button == MouseButton_Left) - { - size_t selected; - - if (tool_ == Tool_Windowing) - { - return new RadiographyWindowingTracker( - undoRedoStack_, - widget.GetScene(), - widget, - OrthancStone::ImageInterpolation_Nearest, - viewportX, viewportY, - RadiographyWindowingTracker::Action_DecreaseWidth, - RadiographyWindowingTracker::Action_IncreaseWidth, - RadiographyWindowingTracker::Action_DecreaseCenter, - RadiographyWindowingTracker::Action_IncreaseCenter); - } - else if (!widget.LookupSelectedLayer(selected)) - { - // No layer is currently selected - size_t layer; - if (widget.GetScene().LookupLayer(layer, x, y)) - { - widget.Select(layer); - } - - return NULL; - } - else if (tool_ == Tool_Crop || - tool_ == Tool_Resize || - tool_ == Tool_Mask) - { - RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected); - - ControlPoint controlPoint; - if (accessor.GetLayer().LookupControlPoint(controlPoint, x, y, view.GetZoom(), GetHandleSize())) - { - switch (tool_) - { - case Tool_Crop: - return new RadiographyLayerCropTracker - (undoRedoStack_, widget.GetScene(), view, selected, controlPoint); - - case Tool_Mask: - return new RadiographyLayerMaskTracker - (undoRedoStack_, widget.GetScene(), view, selected, controlPoint); - - case Tool_Resize: - return new RadiographyLayerResizeTracker - (undoRedoStack_, widget.GetScene(), selected, controlPoint, - (modifiers & KeyboardModifiers_Shift)); - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - else - { - size_t layer; - - if (widget.GetScene().LookupLayer(layer, x, y)) - { - widget.Select(layer); - } - else - { - widget.Unselect(); - } - - return NULL; - } - } - else - { - size_t layer; - - if (widget.GetScene().LookupLayer(layer, x, y)) - { - if (layer == selected) - { - switch (tool_) - { - case Tool_Move: - return new RadiographyLayerMoveTracker - (undoRedoStack_, widget.GetScene(), layer, x, y, - (modifiers & KeyboardModifiers_Shift)); - - case Tool_Rotate: - return new RadiographyLayerRotateTracker - (undoRedoStack_, widget.GetScene(), view, layer, x, y, - (modifiers & KeyboardModifiers_Shift)); - - default: - break; - } - - return NULL; - } - else - { - widget.Select(layer); - return NULL; - } - } - else - { - widget.Unselect(); - return NULL; - } - } - } - else - { - return NULL; - } - return NULL; - } - - virtual void MouseOver(CairoContext& context, - Deprecated::WorldSceneWidget& worldWidget, - const Deprecated::ViewportGeometry& view, - double x, - double y, - Deprecated::IStatusBar* statusBar) - { - RadiographyWidget& widget = dynamic_cast(worldWidget); - -#if 0 - if (statusBar != NULL) - { - char buf[64]; - sprintf(buf, "X = %.02f Y = %.02f (in cm)", x / 10.0, y / 10.0); - statusBar->SetMessage(buf); - } -#endif - - size_t selected; - - if (widget.LookupSelectedLayer(selected) && - (tool_ == Tool_Crop || - tool_ == Tool_Resize || - tool_ == Tool_Mask)) - { - RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected); - - ControlPoint controlPoint; - if (accessor.GetLayer().LookupControlPoint(controlPoint, x, y, view.GetZoom(), GetHandleSize())) - { - double z = 1.0 / view.GetZoom(); - - context.SetSourceColor(255, 0, 0); - cairo_t* cr = context.GetObject(); - cairo_set_line_width(cr, 2.0 * z); - cairo_move_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y - GetHandleSize() * z); - cairo_line_to(cr, controlPoint.x + GetHandleSize() * z, controlPoint.y - GetHandleSize() * z); - cairo_line_to(cr, controlPoint.x + GetHandleSize() * z, controlPoint.y + GetHandleSize() * z); - cairo_line_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y + GetHandleSize() * z); - cairo_line_to(cr, controlPoint.x - GetHandleSize() * z, controlPoint.y - GetHandleSize() * z); - cairo_stroke(cr); - } - } - } - - virtual void MouseWheel(Deprecated::WorldSceneWidget& widget, - MouseWheelDirection direction, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - { - } - - virtual void KeyPressed(Deprecated::WorldSceneWidget& worldWidget, - KeyboardKeys key, - char keyChar, - KeyboardModifiers modifiers, - Deprecated::IStatusBar* statusBar) - { - RadiographyWidget& widget = dynamic_cast(worldWidget); - - switch (keyChar) - { - case 'a': - widget.FitContent(); - break; - - case 'c': - tool_ = Tool_Crop; - break; - - case 'm': - tool_ = Tool_Mask; - widget.Select(1); - break; - - case 'd': - { - // dump to json and reload - Json::Value snapshot; - RadiographySceneWriter writer; - writer.Write(snapshot, widget.GetScene()); - - LOG(INFO) << "JSON export was successful: " - << snapshot.toStyledString(); - - boost::shared_ptr scene(new RadiographyScene(GetBroker())); - RadiographySceneReader reader(*scene, context_->GetOrthancApiClient()); - reader.Read(snapshot); - - widget.SetScene(scene); - };break; - - case 'e': - { - Orthanc::DicomMap tags; - - // Minimal set of tags to generate a valid CR image - tags.SetValue(Orthanc::DICOM_TAG_ACCESSION_NUMBER, "NOPE", false); - tags.SetValue(Orthanc::DICOM_TAG_BODY_PART_EXAMINED, "PELVIS", false); - tags.SetValue(Orthanc::DICOM_TAG_INSTANCE_NUMBER, "1", false); - //tags.SetValue(Orthanc::DICOM_TAG_LATERALITY, "", false); - tags.SetValue(Orthanc::DICOM_TAG_MANUFACTURER, "OSIMIS", false); - tags.SetValue(Orthanc::DICOM_TAG_MODALITY, "CR", false); - tags.SetValue(Orthanc::DICOM_TAG_PATIENT_BIRTH_DATE, "20000101", false); - tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ID, "hello", false); - tags.SetValue(Orthanc::DICOM_TAG_PATIENT_NAME, "HELLO^WORLD", false); - tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ORIENTATION, "", false); - tags.SetValue(Orthanc::DICOM_TAG_PATIENT_SEX, "M", false); - tags.SetValue(Orthanc::DICOM_TAG_REFERRING_PHYSICIAN_NAME, "HOUSE^MD", false); - tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false); - tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false); - tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false); - tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false); - - if (context_ != NULL) - { - widget.GetScene().ExportDicom(context_->GetOrthancApiClient(), - tags, std::string(), 0.1, 0.1, widget.IsInverted(), - false /* autoCrop */, widget.GetInterpolation(), EXPORT_USING_PAM); - } - - break; - } - - case 'i': - widget.SwitchInvert(); - break; - - case 't': - tool_ = Tool_Move; - break; - - case 'n': - { - switch (widget.GetInterpolation()) - { - case ImageInterpolation_Nearest: - LOG(INFO) << "Switching to bilinear interpolation"; - widget.SetInterpolation(ImageInterpolation_Bilinear); - break; - - case ImageInterpolation_Bilinear: - LOG(INFO) << "Switching to nearest neighbor interpolation"; - widget.SetInterpolation(ImageInterpolation_Nearest); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - break; - } - - case 'r': - tool_ = Tool_Rotate; - break; - - case 's': - tool_ = Tool_Resize; - break; - - case 'w': - tool_ = Tool_Windowing; - break; - - case 'y': - if (modifiers & KeyboardModifiers_Control) - { - undoRedoStack_.Redo(); - widget.NotifyContentChanged(); - } - break; - - case 'z': - if (modifiers & KeyboardModifiers_Control) - { - undoRedoStack_.Undo(); - widget.NotifyContentChanged(); - } - break; - - default: - break; - } - } - }; - - - - class SingleFrameEditorApplication : - public SampleSingleCanvasApplicationBase, - public IObserver - { - private: - boost::shared_ptr scene_; - RadiographyEditorInteractor interactor_; - Orthanc::FontRegistry fontRegistry_; - RadiographyMaskLayer* maskLayer_; - - public: - SingleFrameEditorApplication(MessageBroker& broker) : - IObserver(broker), - interactor_(broker) - { - } - - virtual ~SingleFrameEditorApplication() - { - LOG(WARNING) << "Destroying the application"; - } - - virtual void DeclareStartupOptions(boost::program_options::options_description& options) - { - boost::program_options::options_description generic("Sample options"); - generic.add_options() - ("instance", boost::program_options::value(), - "Orthanc ID of the instance") - ("frame", boost::program_options::value()->default_value(0), - "Number of the frame, for multi-frame DICOM instances") - ; - - options.add(generic); - } - - virtual void Initialize(StoneApplicationContext* context, - Deprecated::IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - using namespace OrthancStone; - - context_ = context; - interactor_.SetContext(*context); - - statusBar.SetMessage("Use the key \"a\" to reinitialize the layout"); - statusBar.SetMessage("Use the key \"c\" to crop"); - statusBar.SetMessage("Use the key \"e\" to export DICOM to the Orthanc server"); - statusBar.SetMessage("Use the key \"f\" to switch full screen"); - statusBar.SetMessage("Use the key \"i\" to invert contrast"); - statusBar.SetMessage("Use the key \"m\" to modify the mask"); - statusBar.SetMessage("Use the key \"n\" to switch between nearest neighbor and bilinear interpolation"); - statusBar.SetMessage("Use the key \"r\" to rotate objects"); - statusBar.SetMessage("Use the key \"s\" to resize objects (not applicable to DICOM layers)"); - statusBar.SetMessage("Use the key \"t\" to move (translate) objects"); - statusBar.SetMessage("Use the key \"w\" to change windowing"); - - statusBar.SetMessage("Use the key \"ctrl-z\" to undo action"); - statusBar.SetMessage("Use the key \"ctrl-y\" to redo action"); - - if (parameters.count("instance") != 1) - { - LOG(ERROR) << "The instance ID is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - std::string instance = parameters["instance"].as(); - //int frame = parameters["frame"].as(); - - scene_.reset(new RadiographyScene(GetBroker())); - - RadiographyLayer& dicomLayer = scene_->LoadDicomFrame(context->GetOrthancApiClient(), instance, 0, false, NULL); - //scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0); - // = scene_->LoadDicomFrame(context->GetOrthancApiClient(), "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false, NULL); - -#if !defined(ORTHANC_ENABLE_WASM) || ORTHANC_ENABLE_WASM != 1 - Orthanc::HttpClient::ConfigureSsl(true, "/etc/ssl/certs/ca-certificates.crt"); -#endif - - //scene_->LoadDicomWebFrame(context->GetWebService()); - - std::vector mask; - mask.push_back(Orthanc::ImageProcessing::ImagePoint(1100, 100)); - mask.push_back(Orthanc::ImageProcessing::ImagePoint(1100, 1000)); - mask.push_back(Orthanc::ImageProcessing::ImagePoint(2000, 1000)); - mask.push_back(Orthanc::ImageProcessing::ImagePoint(2200, 150)); - mask.push_back(Orthanc::ImageProcessing::ImagePoint(1500, 550)); - maskLayer_ = dynamic_cast(&(scene_->LoadMask(mask, dynamic_cast(dicomLayer), 128.0f, NULL))); - interactor_.SetMaskLayer(maskLayer_); - - { - std::unique_ptr renderedTextAlpha(TextRenderer::Render(Orthanc::EmbeddedResources::UBUNTU_FONT, 100, - "%öÇaA&#")); - RadiographyLayer& layer = scene_->LoadAlphaBitmap(renderedTextAlpha.release(), NULL); - dynamic_cast(layer).SetForegroundValue(200.0f * 256.0f); - } - - { - RadiographyTextLayer::RegisterFont("ubuntu", Orthanc::EmbeddedResources::UBUNTU_FONT); - RadiographyLayer& layer = scene_->LoadText("Hello\nworld", "ubuntu", 20, 128, NULL, false); - layer.SetResizeable(true); - } - - { - RadiographyLayer& layer = scene_->LoadTestBlock(100, 50, NULL); - layer.SetResizeable(true); - layer.SetPan(0, 200); - } - - - mainWidget_ = new RadiographyWidget(GetBroker(), scene_, "main-widget"); - mainWidget_->SetTransmitMouseOver(true); - mainWidget_->SetInteractor(interactor_); - - //scene_->SetWindowing(128, 256); - } - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SingleVolumeApplication.h --- a/Applications/Samples/SingleVolumeApplication.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,277 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "SampleApplicationBase.h" -#include "../../Framework/dev.h" -#include "../../Framework/Layers/LineMeasureTracker.h" -#include "../../Framework/Layers/CircleMeasureTracker.h" - -#include -#include - -#include // TODO REMOVE -#include "../../Framework/Layers/DicomStructureSetSlicer.h" // TODO REMOVE -#include "../../Framework/Toolbox/MessagingToolbox.h" // TODO REMOVE - -namespace OrthancStone -{ - namespace Samples - { - class SingleVolumeApplication : public SampleApplicationBase - { - private: - class Interactor : public VolumeImageInteractor - { - private: - SliceViewerWidget& widget_; - size_t layer_; - - protected: - virtual void NotifySliceContentChange(const ISlicedVolume& volume, - const size_t& sliceIndex, - const Slice& slice) - { - const OrthancVolumeImage& image = dynamic_cast(volume); - - RenderStyle s = widget_.GetLayerStyle(layer_); - - if (image.FitWindowingToRange(s, slice.GetConverter())) - { - //printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); - widget_.SetLayerStyle(layer_, s); - } - } - - virtual void MouseOver(CairoContext& context, - WorldSceneWidget& widget, - const ViewportGeometry& view, - double x, - double y, - IStatusBar* statusBar) - { - const SliceViewerWidget& w = dynamic_cast(widget); - Vector p = w.GetSlice().MapSliceToWorldCoordinates(x, y); - printf("%f %f %f\n", p[0], p[1], p[2]); - } - - public: - Interactor(OrthancVolumeImage& volume, - SliceViewerWidget& widget, - VolumeProjection projection, - size_t layer) : - VolumeImageInteractor(volume, widget, projection), - widget_(widget), - layer_(layer) - { - } - }; - - - public: - virtual void DeclareStartupOptions(boost::program_options::options_description& options) - { - boost::program_options::options_description generic("Sample options"); - generic.add_options() - ("series", boost::program_options::value(), - "Orthanc ID of the series") - ("instance", boost::program_options::value(), - "Orthanc ID of a multi-frame instance that describes a 3D volume") - ("threads", boost::program_options::value()->default_value(3), - "Number of download threads") - ("projection", boost::program_options::value()->default_value("axial"), - "Projection of interest (can be axial, sagittal or coronal)") - ("reverse", boost::program_options::value()->default_value(false), - "Reverse the normal direction of the volume") - ; - - options.add(generic); - } - - virtual void Initialize(IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - using namespace OrthancStone; - - if (parameters.count("series") > 1 || - parameters.count("instance") > 1) - { - LOG(ERROR) << "Only one series or instance is allowed"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - if (parameters.count("series") == 1 && - parameters.count("instance") == 1) - { - LOG(ERROR) << "Cannot specify both a series and an instance"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - std::string series; - if (parameters.count("series") == 1) - { - series = parameters["series"].as(); - } - - std::string instance; - if (parameters.count("instance") == 1) - { - instance = parameters["instance"].as(); - } - - if (series.empty() && - instance.empty()) - { - LOG(ERROR) << "The series ID or instance ID is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - //unsigned int threads = parameters["threads"].as(); - //bool reverse = parameters["reverse"].as(); - - std::string tmp = parameters["projection"].as(); - Orthanc::Toolbox::ToLowerCase(tmp); - - VolumeProjection projection; - if (tmp == "axial") - { - projection = VolumeProjection_Axial; - } - else if (tmp == "sagittal") - { - projection = VolumeProjection_Sagittal; - } - else if (tmp == "coronal") - { - projection = VolumeProjection_Coronal; - } - else - { - LOG(ERROR) << "Unknown projection: " << tmp; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - std::unique_ptr widget(new SliceViewerWidget); - -#if 1 - std::unique_ptr volume(new OrthancVolumeImage(context.GetWebService(), true)); - if (series.empty()) - { - volume->ScheduleLoadInstance(instance); - } - else - { - volume->ScheduleLoadSeries(series); - } - - widget->AddLayer(new VolumeImageMPRSlicer(*volume)); - - context_->AddInteractor(new Interactor(*volume, *widget, projection, 0)); - context_->AddSlicedVolume(volume.release()); - - if (1) - { - RenderStyle s; - //s.drawGrid_ = true; - s.alpha_ = 1; - s.windowing_ = ImageWindowing_Bone; - widget->SetLayerStyle(0, s); - } - else - { - RenderStyle s; - s.alpha_ = 1; - s.applyLut_ = true; - s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; - s.interpolation_ = ImageInterpolation_Bilinear; - widget->SetLayerStyle(0, s); - } -#else - std::unique_ptr ct(new OrthancVolumeImage(context_->GetWebService(), false)); - //ct->ScheduleLoadSeries("15a6f44a-ac7b88fe-19c462d9-dddd918e-b01550d8"); // 0178023P - //ct->ScheduleLoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d"); - //ct->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // IBA - //ct->ScheduleLoadSeries("03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); // 0522c0001 TCIA - ct->ScheduleLoadSeries("295e8a13-dfed1320-ba6aebb2-9a13e20f-1b3eb953"); // Captain - - std::unique_ptr pet(new OrthancVolumeImage(context_->GetWebService(), true)); - //pet->ScheduleLoadSeries("48d2997f-8e25cd81-dd715b64-bd79cdcc-e8fcee53"); // 0178023P - //pet->ScheduleLoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e"); - //pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1 - //pet->ScheduleLoadInstance("337876a1-a68a9718-f15abccd-38faafa1-b99b496a"); // IBA 2 - //pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 3 - //pet->ScheduleLoadInstance("269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c"); // 0522c0001 TCIA - pet->ScheduleLoadInstance("f080888c-0ab7528a-f7d9c28c-84980eb1-ff3b0ae6"); // Captain 1 - //pet->ScheduleLoadInstance("4f78055b-6499a2c5-1e089290-394acc05-3ec781c1"); // Captain 2 - - std::unique_ptr rtStruct(new StructureSetLoader(context_->GetWebService())); - //rtStruct->ScheduleLoadInstance("c2ebc17b-6b3548db-5e5da170-b8ecab71-ea03add3"); // 0178023P - //rtStruct->ScheduleLoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // IBA - //rtStruct->ScheduleLoadInstance("17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA - rtStruct->ScheduleLoadInstance("96c889ab-29fe5c54-dda6e66c-3949e4da-58f90d75"); // Captain - - widget->AddLayer(new VolumeImageMPRSlicer(*ct)); - widget->AddLayer(new VolumeImageMPRSlicer(*pet)); - widget->AddLayer(new DicomStructureSetSlicer(*rtStruct)); - - context_->AddInteractor(new Interactor(*pet, *widget, projection, 1)); - //context_->AddInteractor(new VolumeImageInteractor(*ct, *widget, projection)); - - context_->AddSlicedVolume(ct.release()); - context_->AddSlicedVolume(pet.release()); - context_->AddVolumeLoader(rtStruct.release()); - - { - RenderStyle s; - //s.drawGrid_ = true; - s.alpha_ = 1; - s.windowing_ = ImageWindowing_Bone; - widget->SetLayerStyle(0, s); - } - - { - RenderStyle s; - //s.drawGrid_ = true; - s.SetColor(255, 0, 0); // Draw missing PET layer in red - s.alpha_ = 0.5; - s.applyLut_ = true; - s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; - s.interpolation_ = ImageInterpolation_Bilinear; - s.windowing_ = ImageWindowing_Custom; - s.customWindowCenter_ = 0; - s.customWindowWidth_ = 128; - widget->SetLayerStyle(1, s); - } -#endif - - - statusBar.SetMessage("Use the keys \"b\", \"l\" and \"d\" to change Hounsfield windowing"); - statusBar.SetMessage("Use the keys \"t\" to track the (X,Y,Z) mouse coordinates"); - statusBar.SetMessage("Use the keys \"m\" to measure distances"); - statusBar.SetMessage("Use the keys \"c\" to draw circles"); - - widget->SetTransmitMouseOver(true); - context_->SetCentralWidget(widget.release()); - } - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/StoneSampleCommands.yml --- a/Applications/Samples/StoneSampleCommands.yml Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# -# 1 2 3 4 5 6 7 8 -# 345678901234567890123456789012345678901234567890123456789012345678901234567890 -# -rootName: StoneSampleCommands - -# +---------------------------------+ -# | Messages from TypeScript to C++ | -# +---------------------------------+ - -enum Tool: - - LineMeasure - - CircleMeasure - - Crop - - Windowing - - Zoom - - Pan - - Move - - Rotate - - Resize - - Mask - -struct SelectTool: - __handler: cpp - tool: Tool - -enum ActionType: - - UndoCrop - - Rotate - - Invert - -struct Action: - __handler: cpp - type: ActionType - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/StoneSampleCommands_generate.py --- a/Applications/Samples/StoneSampleCommands_generate.py Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -import sys -import os - -# add the generation script location to the search paths -sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'Resources', 'CodeGeneration')) - -# import the code generation tooling script -import stonegentool - -schemaFile = os.path.join(os.path.dirname(__file__), 'StoneSampleCommands.yml') -outDir = os.path.dirname(__file__) - -# ignition! -stonegentool.Process(schemaFile, outDir) - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/StoneSampleCommands_generated.hpp --- a/Applications/Samples/StoneSampleCommands_generated.hpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,703 +0,0 @@ -/* - 1 2 3 4 5 6 7 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 - -Generated on 2019-03-18 12:07:42.696093 by stonegentool - -*/ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -//#define STONEGEN_NO_CPP11 1 - -#ifdef STONEGEN_NO_CPP11 -#define StoneSmartPtr std::unique_ptr -#else -#define StoneSmartPtr std::unique_ptr -#endif - -namespace StoneSampleCommands -{ - /** Throws in case of problem */ - inline void _StoneDeserializeValue(int32_t& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue.asInt(); - } - - inline Json::Value _StoneSerializeValue(int32_t value) - { - Json::Value result(value); - return result; - } - - inline void _StoneDeserializeValue(Json::Value& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue; - } - - inline Json::Value _StoneSerializeValue(Json::Value value) - { - return value; - } - - /** Throws in case of problem */ - inline void _StoneDeserializeValue(double& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue.asDouble(); - } - - inline Json::Value _StoneSerializeValue(double value) - { - Json::Value result(value); - return result; - } - - /** Throws in case of problem */ - inline void _StoneDeserializeValue(bool& destValue, const Json::Value& jsonValue) - { - destValue = jsonValue.asBool(); - } - - inline Json::Value _StoneSerializeValue(bool value) - { - Json::Value result(value); - return result; - } - - /** Throws in case of problem */ - inline void _StoneDeserializeValue( - std::string& destValue - , const Json::Value& jsonValue) - { - destValue = jsonValue.asString(); - } - - inline Json::Value _StoneSerializeValue(const std::string& value) - { - // the following is better than - Json::Value result(value.data(),value.data()+value.size()); - return result; - } - - inline std::string MakeIndent(size_t indent) - { - char* txt = reinterpret_cast(malloc(indent+1)); // NO EXCEPTION BELOW!!!!!!!!!!!! - for(size_t i = 0; i < indent; ++i) - txt[i] = ' '; - txt[indent] = 0; - std::string retVal(txt); - free(txt); // NO EXCEPTION ABOVE !!!!!!!!!! - return retVal; - } - - // generic dumper - template - std::ostream& StoneDumpValue(std::ostream& out, const T& value, size_t indent) - { - out << MakeIndent(indent) << value; - return out; - } - - // string dumper - inline std::ostream& StoneDumpValue(std::ostream& out, const std::string& value, size_t indent) - { - out << MakeIndent(indent) << "\"" << value << "\""; - return out; - } - - /** Throws in case of problem */ - template - void _StoneDeserializeValue( - std::map& destValue, const Json::Value& jsonValue) - { - destValue.clear(); - for ( - Json::Value::const_iterator itr = jsonValue.begin(); - itr != jsonValue.end(); - itr++) - { - std::string key; - _StoneDeserializeValue(key, itr.key()); - - T innerDestValue; - _StoneDeserializeValue(innerDestValue, *itr); - - destValue[key] = innerDestValue; - } - } - - template - Json::Value _StoneSerializeValue(const std::map& value) - { - Json::Value result(Json::objectValue); - - for (typename std::map::const_iterator it = value.cbegin(); - it != value.cend(); ++it) - { - // it->first it->second - result[it->first] = _StoneSerializeValue(it->second); - } - return result; - } - - template - std::ostream& StoneDumpValue(std::ostream& out, const std::map& value, size_t indent) - { - out << MakeIndent(indent) << "{\n"; - for (typename std::map::const_iterator it = value.cbegin(); - it != value.cend(); ++it) - { - out << MakeIndent(indent+2) << "\"" << it->first << "\" : "; - StoneDumpValue(out, it->second, indent+2); - } - out << MakeIndent(indent) << "}\n"; - return out; - } - - /** Throws in case of problem */ - template - void _StoneDeserializeValue( - std::vector& destValue, const Json::Value& jsonValue) - { - destValue.clear(); - destValue.reserve(jsonValue.size()); - for (Json::Value::ArrayIndex i = 0; i != jsonValue.size(); i++) - { - T innerDestValue; - _StoneDeserializeValue(innerDestValue, jsonValue[i]); - destValue.push_back(innerDestValue); - } - } - - template - Json::Value _StoneSerializeValue(const std::vector& value) - { - Json::Value result(Json::arrayValue); - for (size_t i = 0; i < value.size(); ++i) - { - result.append(_StoneSerializeValue(value[i])); - } - return result; - } - - template - std::ostream& StoneDumpValue(std::ostream& out, const std::vector& value, size_t indent) - { - out << MakeIndent(indent) << "[\n"; - for (size_t i = 0; i < value.size(); ++i) - { - StoneDumpValue(out, value[i], indent+2); - } - out << MakeIndent(indent) << "]\n"; - return out; - } - - inline void StoneCheckSerializedValueTypeGeneric(const Json::Value& value) - { - if ((!value.isMember("type")) || (!value["type"].isString())) - { - std::stringstream ss; - ss << "Cannot deserialize value ('type' key invalid)"; - throw std::runtime_error(ss.str()); - } - } - - inline void StoneCheckSerializedValueType( - const Json::Value& value, std::string typeStr) - { - StoneCheckSerializedValueTypeGeneric(value); - - std::string actTypeStr = value["type"].asString(); - if (actTypeStr != typeStr) - { - std::stringstream ss; - ss << "Cannot deserialize type" << actTypeStr - << "into " << typeStr; - throw std::runtime_error(ss.str()); - } - } - - // end of generic methods - -// end of generic methods - - enum Tool { - Tool_LineMeasure, - Tool_CircleMeasure, - Tool_Crop, - Tool_Windowing, - Tool_Zoom, - Tool_Pan, - Tool_Move, - Tool_Rotate, - Tool_Resize, - Tool_Mask, - }; - - inline std::string ToString(const Tool& value) - { - if( value == Tool_LineMeasure) - { - return std::string("LineMeasure"); - } - if( value == Tool_CircleMeasure) - { - return std::string("CircleMeasure"); - } - if( value == Tool_Crop) - { - return std::string("Crop"); - } - if( value == Tool_Windowing) - { - return std::string("Windowing"); - } - if( value == Tool_Zoom) - { - return std::string("Zoom"); - } - if( value == Tool_Pan) - { - return std::string("Pan"); - } - if( value == Tool_Move) - { - return std::string("Move"); - } - if( value == Tool_Rotate) - { - return std::string("Rotate"); - } - if( value == Tool_Resize) - { - return std::string("Resize"); - } - if( value == Tool_Mask) - { - return std::string("Mask"); - } - std::stringstream ss; - ss << "Value \"" << value << "\" cannot be converted to Tool. Possible values are: " - << " LineMeasure = " << static_cast(Tool_LineMeasure) << ", " - << " CircleMeasure = " << static_cast(Tool_CircleMeasure) << ", " - << " Crop = " << static_cast(Tool_Crop) << ", " - << " Windowing = " << static_cast(Tool_Windowing) << ", " - << " Zoom = " << static_cast(Tool_Zoom) << ", " - << " Pan = " << static_cast(Tool_Pan) << ", " - << " Move = " << static_cast(Tool_Move) << ", " - << " Rotate = " << static_cast(Tool_Rotate) << ", " - << " Resize = " << static_cast(Tool_Resize) << ", " - << " Mask = " << static_cast(Tool_Mask) << ", " - << std::endl; - std::string msg = ss.str(); - throw std::runtime_error(msg); - } - - inline void FromString(Tool& value, std::string strValue) - { - if( strValue == std::string("LineMeasure") ) - { - value = Tool_LineMeasure; - return; - } - if( strValue == std::string("CircleMeasure") ) - { - value = Tool_CircleMeasure; - return; - } - if( strValue == std::string("Crop") ) - { - value = Tool_Crop; - return; - } - if( strValue == std::string("Windowing") ) - { - value = Tool_Windowing; - return; - } - if( strValue == std::string("Zoom") ) - { - value = Tool_Zoom; - return; - } - if( strValue == std::string("Pan") ) - { - value = Tool_Pan; - return; - } - if( strValue == std::string("Move") ) - { - value = Tool_Move; - return; - } - if( strValue == std::string("Rotate") ) - { - value = Tool_Rotate; - return; - } - if( strValue == std::string("Resize") ) - { - value = Tool_Resize; - return; - } - if( strValue == std::string("Mask") ) - { - value = Tool_Mask; - return; - } - - std::stringstream ss; - ss << "String \"" << strValue << "\" cannot be converted to Tool. Possible values are: LineMeasure CircleMeasure Crop Windowing Zoom Pan Move Rotate Resize Mask "; - std::string msg = ss.str(); - throw std::runtime_error(msg); - } - - - inline void _StoneDeserializeValue( - Tool& destValue, const Json::Value& jsonValue) - { - FromString(destValue, jsonValue.asString()); - } - - inline Json::Value _StoneSerializeValue(const Tool& value) - { - std::string strValue = ToString(value); - return Json::Value(strValue); - } - - inline std::ostream& StoneDumpValue(std::ostream& out, const Tool& value, size_t indent = 0) - { - if( value == Tool_LineMeasure) - { - out << MakeIndent(indent) << "LineMeasure" << std::endl; - } - if( value == Tool_CircleMeasure) - { - out << MakeIndent(indent) << "CircleMeasure" << std::endl; - } - if( value == Tool_Crop) - { - out << MakeIndent(indent) << "Crop" << std::endl; - } - if( value == Tool_Windowing) - { - out << MakeIndent(indent) << "Windowing" << std::endl; - } - if( value == Tool_Zoom) - { - out << MakeIndent(indent) << "Zoom" << std::endl; - } - if( value == Tool_Pan) - { - out << MakeIndent(indent) << "Pan" << std::endl; - } - if( value == Tool_Move) - { - out << MakeIndent(indent) << "Move" << std::endl; - } - if( value == Tool_Rotate) - { - out << MakeIndent(indent) << "Rotate" << std::endl; - } - if( value == Tool_Resize) - { - out << MakeIndent(indent) << "Resize" << std::endl; - } - if( value == Tool_Mask) - { - out << MakeIndent(indent) << "Mask" << std::endl; - } - return out; - } - - - enum ActionType { - ActionType_UndoCrop, - ActionType_Rotate, - ActionType_Invert, - }; - - inline std::string ToString(const ActionType& value) - { - if( value == ActionType_UndoCrop) - { - return std::string("UndoCrop"); - } - if( value == ActionType_Rotate) - { - return std::string("Rotate"); - } - if( value == ActionType_Invert) - { - return std::string("Invert"); - } - std::stringstream ss; - ss << "Value \"" << value << "\" cannot be converted to ActionType. Possible values are: " - << " UndoCrop = " << static_cast(ActionType_UndoCrop) << ", " - << " Rotate = " << static_cast(ActionType_Rotate) << ", " - << " Invert = " << static_cast(ActionType_Invert) << ", " - << std::endl; - std::string msg = ss.str(); - throw std::runtime_error(msg); - } - - inline void FromString(ActionType& value, std::string strValue) - { - if( strValue == std::string("UndoCrop") ) - { - value = ActionType_UndoCrop; - return; - } - if( strValue == std::string("Rotate") ) - { - value = ActionType_Rotate; - return; - } - if( strValue == std::string("Invert") ) - { - value = ActionType_Invert; - return; - } - - std::stringstream ss; - ss << "String \"" << strValue << "\" cannot be converted to ActionType. Possible values are: UndoCrop Rotate Invert "; - std::string msg = ss.str(); - throw std::runtime_error(msg); - } - - - inline void _StoneDeserializeValue( - ActionType& destValue, const Json::Value& jsonValue) - { - FromString(destValue, jsonValue.asString()); - } - - inline Json::Value _StoneSerializeValue(const ActionType& value) - { - std::string strValue = ToString(value); - return Json::Value(strValue); - } - - inline std::ostream& StoneDumpValue(std::ostream& out, const ActionType& value, size_t indent = 0) - { - if( value == ActionType_UndoCrop) - { - out << MakeIndent(indent) << "UndoCrop" << std::endl; - } - if( value == ActionType_Rotate) - { - out << MakeIndent(indent) << "Rotate" << std::endl; - } - if( value == ActionType_Invert) - { - out << MakeIndent(indent) << "Invert" << std::endl; - } - return out; - } - - - -#ifdef _MSC_VER -#pragma region SelectTool -#endif //_MSC_VER - - struct SelectTool - { - Tool tool; - - SelectTool(Tool tool = Tool()) - { - this->tool = tool; - } - }; - - inline void _StoneDeserializeValue(SelectTool& destValue, const Json::Value& value) - { - _StoneDeserializeValue(destValue.tool, value["tool"]); - } - - inline Json::Value _StoneSerializeValue(const SelectTool& value) - { - Json::Value result(Json::objectValue); - result["tool"] = _StoneSerializeValue(value.tool); - - return result; - } - - inline std::ostream& StoneDumpValue(std::ostream& out, const SelectTool& value, size_t indent = 0) - { - out << MakeIndent(indent) << "{\n"; - out << MakeIndent(indent) << "tool:\n"; - StoneDumpValue(out, value.tool,indent+2); - out << "\n"; - - out << MakeIndent(indent) << "}\n"; - return out; - } - - inline void StoneDeserialize(SelectTool& destValue, const Json::Value& value) - { - StoneCheckSerializedValueType(value, "StoneSampleCommands.SelectTool"); - _StoneDeserializeValue(destValue, value["value"]); - } - - inline Json::Value StoneSerializeToJson(const SelectTool& value) - { - Json::Value result(Json::objectValue); - result["type"] = "StoneSampleCommands.SelectTool"; - result["value"] = _StoneSerializeValue(value); - return result; - } - - inline std::string StoneSerialize(const SelectTool& value) - { - Json::Value resultJson = StoneSerializeToJson(value); - std::string resultStr = resultJson.toStyledString(); - return resultStr; - } - -#ifdef _MSC_VER -#pragma endregion SelectTool -#endif //_MSC_VER - -#ifdef _MSC_VER -#pragma region Action -#endif //_MSC_VER - - struct Action - { - ActionType type; - - Action(ActionType type = ActionType()) - { - this->type = type; - } - }; - - inline void _StoneDeserializeValue(Action& destValue, const Json::Value& value) - { - _StoneDeserializeValue(destValue.type, value["type"]); - } - - inline Json::Value _StoneSerializeValue(const Action& value) - { - Json::Value result(Json::objectValue); - result["type"] = _StoneSerializeValue(value.type); - - return result; - } - - inline std::ostream& StoneDumpValue(std::ostream& out, const Action& value, size_t indent = 0) - { - out << MakeIndent(indent) << "{\n"; - out << MakeIndent(indent) << "type:\n"; - StoneDumpValue(out, value.type,indent+2); - out << "\n"; - - out << MakeIndent(indent) << "}\n"; - return out; - } - - inline void StoneDeserialize(Action& destValue, const Json::Value& value) - { - StoneCheckSerializedValueType(value, "StoneSampleCommands.Action"); - _StoneDeserializeValue(destValue, value["value"]); - } - - inline Json::Value StoneSerializeToJson(const Action& value) - { - Json::Value result(Json::objectValue); - result["type"] = "StoneSampleCommands.Action"; - result["value"] = _StoneSerializeValue(value); - return result; - } - - inline std::string StoneSerialize(const Action& value) - { - Json::Value resultJson = StoneSerializeToJson(value); - std::string resultStr = resultJson.toStyledString(); - return resultStr; - } - -#ifdef _MSC_VER -#pragma endregion Action -#endif //_MSC_VER - -#ifdef _MSC_VER -#pragma region Dispatching code -#endif //_MSC_VER - - class IHandler - { - public: - virtual bool Handle(const SelectTool& value) = 0; - virtual bool Handle(const Action& value) = 0; - }; - - /** Service function for StoneDispatchToHandler */ - inline bool StoneDispatchJsonToHandler( - const Json::Value& jsonValue, IHandler* handler) - { - StoneCheckSerializedValueTypeGeneric(jsonValue); - std::string type = jsonValue["type"].asString(); - if (type == "") - { - // this should never ever happen - throw std::runtime_error("Caught empty type while dispatching"); - } - else if (type == "StoneSampleCommands.SelectTool") - { - SelectTool value; - _StoneDeserializeValue(value, jsonValue["value"]); - return handler->Handle(value); - } - else if (type == "StoneSampleCommands.Action") - { - Action value; - _StoneDeserializeValue(value, jsonValue["value"]); - return handler->Handle(value); - } - else - { - return false; - } - } - - /** Takes a serialized type and passes this to the handler */ - inline bool StoneDispatchToHandler(std::string strValue, IHandler* handler) - { - Json::Value readValue; - - Json::CharReaderBuilder builder; - Json::CharReader* reader = builder.newCharReader(); - - StoneSmartPtr ptr(reader); - - std::string errors; - - bool ok = reader->parse( - strValue.c_str(), - strValue.c_str() + strValue.size(), - &readValue, - &errors - ); - if (!ok) - { - std::stringstream ss; - ss << "Jsoncpp parsing error: " << errors; - throw std::runtime_error(ss.str()); - } - return StoneDispatchJsonToHandler(readValue, handler); - } - -#ifdef _MSC_VER -#pragma endregion Dispatching code -#endif //_MSC_VER -} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/StoneSampleCommands_generated.ts --- a/Applications/Samples/StoneSampleCommands_generated.ts Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,333 +0,0 @@ -/* - 1 2 3 4 5 6 7 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 - -Generated on 2019-03-18 12:07:42.696093 by stonegentool - -*/ - -function StoneCheckSerializedValueType(value: any, typeStr: string) -{ - StoneCheckSerializedValueTypeGeneric(value); - - if (value['type'] != typeStr) - { - throw new Error( - `Cannot deserialize type ${value['type']} into ${typeStr}`); - } -} - -function isString(val: any) :boolean -{ - return ((typeof val === 'string') || (val instanceof String)); -} - -function StoneCheckSerializedValueTypeGeneric(value: any) -{ - // console.//log("+-------------------------------------------------+"); - // console.//log("| StoneCheckSerializedValueTypeGeneric |"); - // console.//log("+-------------------------------------------------+"); - // console.//log("value = "); - // console.//log(value); - if ( (!('type' in value)) || (!isString(value.type)) ) - { - throw new Error( - "Cannot deserialize value ('type' key invalid)"); - } -} - -// end of generic methods - -export enum Tool { - LineMeasure = "LineMeasure", - CircleMeasure = "CircleMeasure", - Crop = "Crop", - Windowing = "Windowing", - Zoom = "Zoom", - Pan = "Pan", - Move = "Move", - Rotate = "Rotate", - Resize = "Resize", - Mask = "Mask" -}; - -export function Tool_FromString(strValue:string) : Tool -{ - if( strValue == "LineMeasure" ) - { - return Tool.LineMeasure; - } - if( strValue == "CircleMeasure" ) - { - return Tool.CircleMeasure; - } - if( strValue == "Crop" ) - { - return Tool.Crop; - } - if( strValue == "Windowing" ) - { - return Tool.Windowing; - } - if( strValue == "Zoom" ) - { - return Tool.Zoom; - } - if( strValue == "Pan" ) - { - return Tool.Pan; - } - if( strValue == "Move" ) - { - return Tool.Move; - } - if( strValue == "Rotate" ) - { - return Tool.Rotate; - } - if( strValue == "Resize" ) - { - return Tool.Resize; - } - if( strValue == "Mask" ) - { - return Tool.Mask; - } - - let msg : string = `String ${strValue} cannot be converted to Tool. Possible values are: LineMeasure, CircleMeasure, Crop, Windowing, Zoom, Pan, Move, Rotate, Resize, Mask`; - throw new Error(msg); -} - -export function Tool_ToString(value:Tool) : string -{ - if( value == Tool.LineMeasure ) - { - return "LineMeasure"; - } - if( value == Tool.CircleMeasure ) - { - return "CircleMeasure"; - } - if( value == Tool.Crop ) - { - return "Crop"; - } - if( value == Tool.Windowing ) - { - return "Windowing"; - } - if( value == Tool.Zoom ) - { - return "Zoom"; - } - if( value == Tool.Pan ) - { - return "Pan"; - } - if( value == Tool.Move ) - { - return "Move"; - } - if( value == Tool.Rotate ) - { - return "Rotate"; - } - if( value == Tool.Resize ) - { - return "Resize"; - } - if( value == Tool.Mask ) - { - return "Mask"; - } - - let msg : string = `Value ${value} cannot be converted to Tool. Possible values are: `; - { - let _LineMeasure_enumValue : string = Tool.LineMeasure; // enums are strings in stonecodegen, so this will work. - let msg_LineMeasure : string = `LineMeasure (${_LineMeasure_enumValue}), `; - msg = msg + msg_LineMeasure; - } - { - let _CircleMeasure_enumValue : string = Tool.CircleMeasure; // enums are strings in stonecodegen, so this will work. - let msg_CircleMeasure : string = `CircleMeasure (${_CircleMeasure_enumValue}), `; - msg = msg + msg_CircleMeasure; - } - { - let _Crop_enumValue : string = Tool.Crop; // enums are strings in stonecodegen, so this will work. - let msg_Crop : string = `Crop (${_Crop_enumValue}), `; - msg = msg + msg_Crop; - } - { - let _Windowing_enumValue : string = Tool.Windowing; // enums are strings in stonecodegen, so this will work. - let msg_Windowing : string = `Windowing (${_Windowing_enumValue}), `; - msg = msg + msg_Windowing; - } - { - let _Zoom_enumValue : string = Tool.Zoom; // enums are strings in stonecodegen, so this will work. - let msg_Zoom : string = `Zoom (${_Zoom_enumValue}), `; - msg = msg + msg_Zoom; - } - { - let _Pan_enumValue : string = Tool.Pan; // enums are strings in stonecodegen, so this will work. - let msg_Pan : string = `Pan (${_Pan_enumValue}), `; - msg = msg + msg_Pan; - } - { - let _Move_enumValue : string = Tool.Move; // enums are strings in stonecodegen, so this will work. - let msg_Move : string = `Move (${_Move_enumValue}), `; - msg = msg + msg_Move; - } - { - let _Rotate_enumValue : string = Tool.Rotate; // enums are strings in stonecodegen, so this will work. - let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `; - msg = msg + msg_Rotate; - } - { - let _Resize_enumValue : string = Tool.Resize; // enums are strings in stonecodegen, so this will work. - let msg_Resize : string = `Resize (${_Resize_enumValue}), `; - msg = msg + msg_Resize; - } - { - let _Mask_enumValue : string = Tool.Mask; // enums are strings in stonecodegen, so this will work. - let msg_Mask : string = `Mask (${_Mask_enumValue})`; - msg = msg + msg_Mask; - } - throw new Error(msg); -} - -export enum ActionType { - UndoCrop = "UndoCrop", - Rotate = "Rotate", - Invert = "Invert" -}; - -export function ActionType_FromString(strValue:string) : ActionType -{ - if( strValue == "UndoCrop" ) - { - return ActionType.UndoCrop; - } - if( strValue == "Rotate" ) - { - return ActionType.Rotate; - } - if( strValue == "Invert" ) - { - return ActionType.Invert; - } - - let msg : string = `String ${strValue} cannot be converted to ActionType. Possible values are: UndoCrop, Rotate, Invert`; - throw new Error(msg); -} - -export function ActionType_ToString(value:ActionType) : string -{ - if( value == ActionType.UndoCrop ) - { - return "UndoCrop"; - } - if( value == ActionType.Rotate ) - { - return "Rotate"; - } - if( value == ActionType.Invert ) - { - return "Invert"; - } - - let msg : string = `Value ${value} cannot be converted to ActionType. Possible values are: `; - { - let _UndoCrop_enumValue : string = ActionType.UndoCrop; // enums are strings in stonecodegen, so this will work. - let msg_UndoCrop : string = `UndoCrop (${_UndoCrop_enumValue}), `; - msg = msg + msg_UndoCrop; - } - { - let _Rotate_enumValue : string = ActionType.Rotate; // enums are strings in stonecodegen, so this will work. - let msg_Rotate : string = `Rotate (${_Rotate_enumValue}), `; - msg = msg + msg_Rotate; - } - { - let _Invert_enumValue : string = ActionType.Invert; // enums are strings in stonecodegen, so this will work. - let msg_Invert : string = `Invert (${_Invert_enumValue})`; - msg = msg + msg_Invert; - } - throw new Error(msg); -} - - - -export class SelectTool { - tool:Tool; - - constructor() { - } - - public StoneSerialize(): string { - let container: object = {}; - container['type'] = 'StoneSampleCommands.SelectTool'; - container['value'] = this; - return JSON.stringify(container); - } - - public static StoneDeserialize(valueStr: string) : SelectTool - { - let value: any = JSON.parse(valueStr); - StoneCheckSerializedValueType(value, 'StoneSampleCommands.SelectTool'); - let result: SelectTool = value['value'] as SelectTool; - return result; - } -} -export class Action { - type:ActionType; - - constructor() { - } - - public StoneSerialize(): string { - let container: object = {}; - container['type'] = 'StoneSampleCommands.Action'; - container['value'] = this; - return JSON.stringify(container); - } - - public static StoneDeserialize(valueStr: string) : Action - { - let value: any = JSON.parse(valueStr); - StoneCheckSerializedValueType(value, 'StoneSampleCommands.Action'); - let result: Action = value['value'] as Action; - return result; - } -} - -export interface IHandler { -}; - -/** Service function for StoneDispatchToHandler */ -export function StoneDispatchJsonToHandler( - jsonValue: any, handler: IHandler): boolean -{ - StoneCheckSerializedValueTypeGeneric(jsonValue); - let type: string = jsonValue["type"]; - if (type == "") - { - // this should never ever happen - throw new Error("Caught empty type while dispatching"); - } - else - { - return false; - } -} - -/** Takes a serialized type and passes this to the handler */ -export function StoneDispatchToHandler( - strValue: string, handler: IHandler): boolean -{ - // console.//log("+------------------------------------------------+"); - // console.//log("| StoneDispatchToHandler |"); - // console.//log("+------------------------------------------------+"); - // console.//log("strValue = "); - // console.//log(strValue); - let jsonValue: any = JSON.parse(strValue) - return StoneDispatchJsonToHandler(jsonValue, handler); -} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/SynchronizedSeriesApplication.h --- a/Applications/Samples/SynchronizedSeriesApplication.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "SampleInteractor.h" - -#include "../../Framework/Toolbox/OrthancSeriesLoader.h" -#include "../../Framework/Layers/SeriesFrameRendererFactory.h" -#include "../../Framework/Layers/ReferenceLineFactory.h" -#include "../../Framework/Widgets/LayoutWidget.h" - -#include - -namespace OrthancStone -{ - namespace Samples - { - class SynchronizedSeriesApplication : public SampleApplicationBase - { - private: - LayeredSceneWidget* CreateSeriesWidget(BasicApplicationContext& context, - const std::string& series) - { - std::unique_ptr loader - (new OrthancSeriesLoader(context.GetWebService().GetConnection(), series)); - - std::unique_ptr interactor(new SampleInteractor(*loader, false)); - - std::unique_ptr widget(new LayeredSceneWidget); - widget->AddLayer(new SeriesFrameRendererFactory(loader.release(), false)); - widget->SetSlice(interactor->GetCursor().GetCurrentSlice()); - widget->SetInteractor(*interactor); - - context.AddInteractor(interactor.release()); - - return widget.release(); - } - - public: - virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) - { - boost::program_options::options_description generic("Sample options"); - generic.add_options() - ("a", boost::program_options::value(), - "Orthanc ID of the 1st series") - ("b", boost::program_options::value(), - "Orthanc ID of the 2nd series") - ("c", boost::program_options::value(), - "Orthanc ID of the 3rd series") - ; - - options.add(generic); - } - - virtual void Initialize(BasicApplicationContext& context, - IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - if (parameters.count("a") != 1 || - parameters.count("b") != 1 || - parameters.count("c") != 1) - { - LOG(ERROR) << "At least one of the three series IDs is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - std::unique_ptr a(CreateSeriesWidget(context, parameters["a"].as())); - std::unique_ptr b(CreateSeriesWidget(context, parameters["b"].as())); - std::unique_ptr c(CreateSeriesWidget(context, parameters["c"].as())); - - ReferenceLineFactory::Configure(*a, *b); - ReferenceLineFactory::Configure(*a, *c); - ReferenceLineFactory::Configure(*b, *c); - - std::unique_ptr layout(new LayoutWidget); - layout->SetPadding(5); - layout->AddWidget(a.release()); - - std::unique_ptr layoutB(new LayoutWidget); - layoutB->SetVertical(); - layoutB->SetPadding(5); - layoutB->AddWidget(b.release()); - layoutB->AddWidget(c.release()); - layout->AddWidget(layoutB.release()); - - context.SetCentralWidget(layout.release()); - } - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/TestPatternApplication.h --- a/Applications/Samples/TestPatternApplication.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "SampleApplicationBase.h" - -#include "../../Framework/Widgets/TestCairoWidget.h" -#include "../../Framework/Widgets/TestWorldSceneWidget.h" -#include "../../Framework/Widgets/LayoutWidget.h" - -namespace OrthancStone -{ - namespace Samples - { - class TestPatternApplication : public SampleApplicationBase - { - public: - virtual void DeclareStartupOptions(boost::program_options::options_description& options) - { - boost::program_options::options_description generic("Sample options"); - generic.add_options() - ("animate", boost::program_options::value()->default_value(true), "Animate the test pattern") - ; - - options.add(generic); - } - - virtual void Initialize(IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - using namespace OrthancStone; - - std::unique_ptr layout(new LayoutWidget); - layout->SetPadding(10); - layout->SetBackgroundCleared(true); - layout->AddWidget(new TestCairoWidget(parameters["animate"].as())); - layout->AddWidget(new TestWorldSceneWidget(parameters["animate"].as())); - - context_->SetCentralWidget(layout.release()); - context_->SetUpdateDelay(25); // If animation, update the content each 25ms - } - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/index.html --- a/Applications/Samples/Web/index.html Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ - - - - - - - - - - - - Wasm Samples - - - - - - \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/samples-styles.css --- a/Applications/Samples/Web/samples-styles.css Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -html, body { - width: 100%; - height: 100%; - margin: 0px; - border: 0; - overflow: hidden; /* Disable scrollbars */ - display: block; /* No floating content on sides */ - background-color: black; - color: white; - font-family: Arial, Helvetica, sans-serif; -} - -canvas { - left:0px; - top:0px; -} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/simple-viewer-single-file.html --- a/Applications/Samples/Web/simple-viewer-single-file.html Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ - - - - - - - - - - - - - Simple Viewer - - - - -
-
- -
-
- -
-
-
- line - circle - - -
- - - - \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/simple-viewer-single-file.ts --- a/Applications/Samples/Web/simple-viewer-single-file.ts Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -import wasmApplicationRunner = require('../../../Platforms/Wasm/wasm-application-runner'); - -wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSimpleViewerSingleFile", "/orthanc"); - -function SelectTool(toolName: string) { - var command = { - command: "selectTool", - args: { - toolName: toolName - } - }; - wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); - -} - -function PerformAction(commandName: string) { - var command = { - command: commandName, - commandType: "simple", - args: {} - }; - wasmApplicationRunner.SendSerializedMessageToStoneApplication(JSON.stringify(command)); -} - -//initializes the buttons -//----------------------- -// install "SelectTool" handlers -document.querySelectorAll("[tool-selector]").forEach((e) => { - console.log(e); - (e as HTMLInputElement).addEventListener("click", () => { - console.log(e); - SelectTool(e.attributes["tool-selector"].value); - }); -}); - -// install "PerformAction" handlers -document.querySelectorAll("[action-trigger]").forEach((e) => { - (e as HTMLInputElement).addEventListener("click", () => { - PerformAction(e.attributes["action-trigger"].value); - }); -}); - -// this method is called "from the C++ code" when the StoneApplication is updated. -// it can be used to update the UI of the application -function UpdateWebApplicationWithString(statusUpdateMessage: string) { - console.log(statusUpdateMessage); - - if (statusUpdateMessage.startsWith("series-description=")) { - document.getElementById("series-description").innerText = statusUpdateMessage.split("=")[1]; - } -} - -function UpdateWebApplicationWithSerializedMessage(statusUpdateMessageString: string) { - console.log("updating web application with serialized message: ", statusUpdateMessageString); - console.log(""); -} - -// make it available to other js scripts in the application -( window).UpdateWebApplicationWithString = UpdateWebApplicationWithString; - -( window).UpdateWebApplicationWithSerializedMessage = UpdateWebApplicationWithSerializedMessage; diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/simple-viewer-single-file.tsconfig.json --- a/Applications/Samples/Web/simple-viewer-single-file.tsconfig.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -{ - "extends" : "./tsconfig-samples", - "compilerOptions": { - // "outFile": "../build-web/app-simple-viewer-single-file.js" - }, - "include" : [ - "simple-viewer-single-file.ts" - ] -} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/single-frame-editor.html --- a/Applications/Samples/Web/single-frame-editor.html Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ - - - - - - - - - - - - Simple Viewer - - - -
- -
- - - - \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/single-frame-editor.ts --- a/Applications/Samples/Web/single-frame-editor.ts Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -import wasmApplicationRunner = require('../../../Platforms/Wasm/wasm-application-runner'); - -wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSingleFrameEditor", "/orthanc"); diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/single-frame-editor.tsconfig.json --- a/Applications/Samples/Web/single-frame-editor.tsconfig.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -{ - "extends" : "./tsconfig-samples", - "compilerOptions": { - }, - "include" : [ - "single-frame-editor.ts" - ] -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/single-frame.html --- a/Applications/Samples/Web/single-frame.html Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ - - - - - - - - - - - - Simple Viewer - - - -
- -
- - - - \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/single-frame.ts --- a/Applications/Samples/Web/single-frame.ts Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -import wasmApplicationRunner = require('../../../Platforms/Wasm/wasm-application-runner'); - -wasmApplicationRunner.InitializeWasmApplication("OrthancStoneSingleFrame", "/orthanc"); - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/single-frame.tsconfig.json --- a/Applications/Samples/Web/single-frame.tsconfig.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -{ - "extends" : "./tsconfig-samples", - "compilerOptions": { - }, - "include" : [ - "single-frame.ts" - ] -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/Web/tsconfig-samples.json --- a/Applications/Samples/Web/tsconfig-samples.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -{ - "extends" : "../../../Platforms/Wasm/tsconfig-stone", - "compilerOptions": { - "sourceMap": false, - "lib" : [ - "es2017", - "dom", - "dom.iterable" - ] - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/build-wasm.sh --- a/Applications/Samples/build-wasm.sh Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -#!/bin/bash -# -# usage: -# to build all targets in Debug: -# ./build-wasm.sh -# -# to build a single target in release: -# ./build-wasm.sh OrthancStoneSingleFrameEditor Release - -set -e - -target=${1:-all} -buildType=${2:-Debug} - -currentDir=$(pwd) -samplesRootDir=$(pwd) - -mkdir -p $samplesRootDir/build-wasm -cd $samplesRootDir/build-wasm - -source ~/apps/emsdk/emsdk_env.sh -cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=~/apps/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=$buildType -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc -DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON -ninja $target - -echo "-- building the web application -- " -cd $currentDir -./build-web.sh \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/build-wasm.sh.old --- a/Applications/Samples/build-wasm.sh.old Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -#!/bin/bash -# -# usage: -# to build all targets: -# ./build-wasm.sh -# -# to build a single target: -# ./build-wasm.sh OrthancStoneSingleFrameEditor - -set -e - -target=${1:-all} - -currentDir=$(pwd) -samplesRootDir=$(pwd) - -mkdir -p $samplesRootDir/build-wasm -cd $samplesRootDir/build-wasm - -source ~/apps/emsdk/emsdk_env.sh -cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DSTONE_SOURCES_DIR=$currentDir/../../../orthanc-stone \ - -DORTHANC_FRAMEWORK_SOURCE=path \ - -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc \ - -DALLOW_DOWNLOADS=ON .. \ - -DENABLE_WASM=ON - -ninja $target - -echo "-- building the web application -- " -cd $currentDir -./build-web.sh diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/build-web-ext.sh --- a/Applications/Samples/build-web-ext.sh Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -#!/bin/bash - -set -e - -target=${1:-all} -# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/ - -currentDir=$(pwd) - -scriptDirRel=$(dirname $0) -#echo $scriptDirRel -scriptDirAbs=$(realpath $scriptDirRel) -echo $scriptDirAbs - -samplesRootDir=scriptDirAbs - -outputDir=$samplesRootDir/build-web/ -mkdir -p $outputDir - -# files used by all single files samples -cp $samplesRootDir/Web/index.html $outputDir -cp $samplesRootDir/Web/samples-styles.css $outputDir - -# build simple-viewer-single-file (obsolete project) -if [[ $target == "all" || $target == "OrthancStoneSimpleViewerSingleFile" ]]; then - cp $samplesRootDir/Web/simple-viewer-single-file.html $outputDir - tsc --allowJs --project $samplesRootDir/Web/simple-viewer-single-file.tsconfig.json - cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.js $outputDir - cp $currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.wasm $outputDir -fi - -# build single-frame -if [[ $target == "all" || $target == "OrthancStoneSingleFrame" ]]; then - cp $samplesRootDir/Web/single-frame.html $outputDir - tsc --allowJs --project $samplesRootDir/Web/single-frame.tsconfig.json - cp $currentDir/build-wasm/OrthancStoneSingleFrame.js $outputDir - cp $currentDir/build-wasm/OrthancStoneSingleFrame.wasm $outputDir -fi - -# build single-frame-editor -if [[ $target == "all" || $target == "OrthancStoneSingleFrameEditor" ]]; then - cp $samplesRootDir/Web/single-frame-editor.html $outputDir - tsc --allowJs --project $samplesRootDir/Web/single-frame-editor.tsconfig.json - cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.js $outputDir - cp $currentDir/build-wasm/OrthancStoneSingleFrameEditor.wasm $outputDir -fi - -# build simple-viewer project -if [[ $target == "all" || $target == "OrthancStoneSimpleViewer" ]]; then - mkdir -p $outputDir/simple-viewer/ - cp $samplesRootDir/SimpleViewer/Wasm/simple-viewer.html $outputDir/simple-viewer/ - cp $samplesRootDir/SimpleViewer/Wasm/styles.css $outputDir/simple-viewer/ - tsc --allowJs --project $samplesRootDir/SimpleViewer/Wasm/tsconfig-simple-viewer.json - cp $currentDir/build-wasm/OrthancStoneSimpleViewer.js $outputDir/simple-viewer/ - cp $currentDir/build-wasm/OrthancStoneSimpleViewer.wasm $outputDir/simple-viewer/ -fi - -cd $currentDir diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/build-web.sh --- a/Applications/Samples/build-web.sh Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -#!/bin/bash - -set -e - -target=${1:-all} -# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/ - -currentDir=$(pwd) -samplesRootDir=$(pwd) - -echo "*************************************************************************" -echo "samplesRootDir = $samplesRootDir" -echo "*************************************************************************" - -outputDir=$samplesRootDir/build-web/ -mkdir -p "$outputDir" - -# files used by all single files samples -cp "$samplesRootDir/Web/index.html" "$outputDir" -cp "$samplesRootDir/Web/samples-styles.css" "$outputDir" - -# # build simple-viewer-single-file (obsolete project) -# if [[ $target == "all" || $target == "OrthancStoneSimpleViewerSingleFile" ]]; then -# cp $samplesRootDir/Web/simple-viewer-single-file.html $outputDir -# tsc --project $samplesRootDir/Web/simple-viewer-single-file.tsconfig.json --outDir "$outputDir" -# browserify \ -# "$outputDir/Platforms/Wasm/wasm-application-runner.js" \ -# "$outputDir/Applications/Samples/Web/simple-viewer-single-file.js" \ -# -o "$outputDir/app-simple-viewer-single-file.js" -# cp "$currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.js" $outputDir -# cp "$currentDir/build-wasm/OrthancStoneSimpleViewerSingleFile.wasm" $outputDir -# fi - -# # build single-frame -# if [[ $target == "all" || $target == "OrthancStoneSingleFrame" ]]; then -# cp $samplesRootDir/Web/single-frame.html $outputDir -# tsc --project $samplesRootDir/Web/single-frame.tsconfig.json --outDir "$outputDir" -# browserify \ -# "$outputDir/Platforms/Wasm/wasm-application-runner.js" \ -# "$outputDir/Applications/Samples/Web/single-frame.js" \ -# -o "$outputDir/app-single-frame.js" -# cp "$currentDir/build-wasm/OrthancStoneSingleFrame.js" $outputDir -# cp "$currentDir/build-wasm/OrthancStoneSingleFrame.wasm" $outputDir -# fi - -# build single-frame-editor -if [[ $target == "all" || $target == "OrthancStoneSingleFrameEditor" ]]; then - cp $samplesRootDir/Web/single-frame-editor.html $outputDir - tsc --project $samplesRootDir/Web/single-frame-editor.tsconfig.json --outDir "$outputDir" - browserify \ - "$outputDir/Platforms/Wasm/wasm-application-runner.js" \ - "$outputDir/Applications/Samples/Web/single-frame-editor.js" \ - -o "$outputDir/app-single-frame-editor.js" - cp "$currentDir/build-wasm/OrthancStoneSingleFrameEditor.js" $outputDir - cp "$currentDir/build-wasm/OrthancStoneSingleFrameEditor.wasm" $outputDir -fi - -# build simple-viewer project -if [[ $target == "all" || $target == "OrthancStoneSimpleViewer" ]]; then - mkdir -p $outputDir/simple-viewer/ - cp $samplesRootDir/SimpleViewer/Wasm/simple-viewer.html $outputDir/simple-viewer/ - cp $samplesRootDir/SimpleViewer/Wasm/styles.css $outputDir/simple-viewer/ - - # the root dir must contain all the source files for the whole project - tsc --module commonjs --allowJs --project "$samplesRootDir/SimpleViewer/Wasm/tsconfig-simple-viewer.json" --rootDir "$samplesRootDir/../.." --outDir "$outputDir/simple-viewer/" - browserify \ - "$outputDir/simple-viewer/Platforms/Wasm/wasm-application-runner.js" \ - "$outputDir/simple-viewer/Applications/Samples/SimpleViewer/Wasm/simple-viewer.js" \ - -o "$outputDir/simple-viewer/app-simple-viewer.js" - cp "$currentDir/build-wasm/OrthancStoneSimpleViewer.js" "$outputDir/simple-viewer/" - cp "$currentDir/build-wasm/OrthancStoneSimpleViewer.wasm" "$outputDir/simple-viewer/" -fi - -cd $currentDir diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/get-requirements-windows.ps1 --- a/Applications/Samples/get-requirements-windows.ps1 Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ - -if ($true) { - - Write-Error "This script is obsolete. Please work under WSL and run build-wasm.sh" - -} else { - - param( - [IO.DirectoryInfo] $EmsdkRootDir = "C:\Emscripten", - [bool] $Overwrite = $false - ) - - if (Test-Path -Path $EmsdkRootDir) { - if( $Override) { - Remove-Item -Path $EmsdkRootDir -Force -Recurse - } else { - throw "The `"$EmsdkRootDir`" folder may not exist! Use the Overwrite flag to bypass this check." - } - } - - # TODO: detect whether git is installed - # choco install -y git - - Write-Host "Will retrieve the Emscripten SDK to the `"$EmsdkRootDir`" folder" - - $EmsdkParentDir = split-path -Parent $EmsdkRootDir - $EmsdkRootName = split-path -Leaf $EmsdkRootDir - - Push-Location $EmsdkParentDir - - git clone https://github.com/juj/emsdk.git $EmsdkRootName - cd $EmsdkRootName - - git pull - - ./emsdk install latest - - ./emsdk activate latest - - echo "INFO: the ~/.emscripten file has been configured for this installation of Emscripten." - - Write-Host "emsdk is now installed in $EmsdkRootDir" - - Pop-Location - -} - - - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/nginx.local.conf --- a/Applications/Samples/nginx.local.conf Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -# Local config to serve the WASM samples static files and reverse proxy Orthanc. -# Uses port 9977 instead of 80. - -# `events` section is mandatory -events { - worker_connections 1024; # Default: 1024 -} - -http { - - # prevent nginx sync issues on OSX - proxy_buffering off; - - server { - listen 9977 default_server; - client_max_body_size 4G; - - # location may have to be adjusted depending on your OS and nginx install - include /etc/nginx/mime.types; - # if not in your system mime.types, add this line to support WASM: - # types { - # application/wasm wasm; - # } - - # serve WASM static files - root build-web/; - location / { - } - - # reverse proxy orthanc - location /orthanc/ { - rewrite /orthanc(.*) $1 break; - proxy_pass http://127.0.0.1:8042; - proxy_set_header Host $http_host; - proxy_set_header my-auth-header good-token; - proxy_request_buffering off; - proxy_max_temp_file_size 0; - client_max_body_size 0; - } - - - } - -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/package-lock.json --- a/Applications/Samples/package-lock.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -{ - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "typescript": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", - "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==" - } - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/CMakeLists.txt --- a/Applications/Samples/rt-viewer-demo/CMakeLists.txt Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) -project(RtViewerDemo) - -if(MSVC) - add_definitions(/MP) - if (CMAKE_BUILD_TYPE MATCHES DEBUG) - add_definitions(/JMC) - endif() -endif() - -message("-------------------------------------------------------------------------------------------------------------------") -message("ORTHANC_FRAMEWORK_ROOT is set to ${ORTHANC_FRAMEWORK_ROOT}") -message("-------------------------------------------------------------------------------------------------------------------") - -if(NOT DEFINED ORTHANC_FRAMEWORK_ROOT) - message(FATAL_ERROR "The location of the Orthanc source repository must be set in the ORTHANC_FRAMEWORK_ROOT CMake variable") -endif() - -message("-------------------------------------------------------------------------------------------------------------------") -message("STONE_SOURCES_DIR is set to ${STONE_SOURCES_DIR}") -message("-------------------------------------------------------------------------------------------------------------------") - -if(NOT DEFINED STONE_SOURCES_DIR) - message(FATAL_ERROR "The location of the Stone of Orthanc source repository must be set in the STONE_SOURCES_DIR CMake variable") -endif() - -include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneParameters.cmake) - -if (OPENSSL_NO_CAPIENG) -add_definitions(-DOPENSSL_NO_CAPIENG=1) -endif() - -set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") -set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") -set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") - -if (ENABLE_WASM) - ##################################################################### - ## Configuration of the Emscripten compiler for WebAssembly target - ##################################################################### - - set(WASM_FLAGS "-s WASM=1") - set(WASM_FLAGS "${WASM_FLAGS} -s STRICT=1") # drops support for all deprecated build options - set(WASM_FLAGS "${WASM_FLAGS} -s FILESYSTEM=1") # if we don't include it, gen_uuid.c fails to build because srand, getpid(), ... are not defined - set(WASM_FLAGS "${WASM_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0") # actually enable exception catching - set(WASM_FLAGS "${WASM_FLAGS} -s ERROR_ON_MISSING_LIBRARIES=1") - - if (CMAKE_BUILD_TYPE MATCHES DEBUG) - set(WASM_FLAGS "${WASM_FLAGS} -g4") # generate debug information - set(WASM_FLAGS "${WASM_FLAGS} -s ASSERTIONS=2") # more runtime checks - else() - set(WASM_FLAGS "${WASM_FLAGS} -Os") # optimize for web (speed and size) - endif() - - set(WASM_MODULE_NAME "StoneFrameworkModule" CACHE STRING "Name of the WebAssembly module") - - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") - - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${WASM_FLAGS}") # not always clear which flags are for the compiler and which one are for the linker -> pass them all to the linker too - # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Applications/Samples/samples-library.js") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmWebService.js") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/WasmDelayedCallExecutor.js") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --js-library ${STONE_SOURCES_DIR}/Platforms/Wasm/default-library.js") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='\"${WASM_MODULE_NAME}\"'") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=536870912") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_STACK=128000000") - - add_definitions(-DORTHANC_ENABLE_WASM=1) - set(ORTHANC_SANDBOXED ON) - -elseif (ENABLE_QT OR ENABLE_SDL) - - set(ENABLE_NATIVE ON) - set(ORTHANC_SANDBOXED OFF) - set(ENABLE_CRYPTO_OPTIONS ON) - set(ENABLE_GOOGLE_TEST ON) - set(ENABLE_WEB_CLIENT ON) - -endif() - - -##################################################################### -## Configuration for Orthanc -##################################################################### - -if (ORTHANC_STONE_VERSION STREQUAL "mainline") - set(ORTHANC_FRAMEWORK_VERSION "mainline") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") -else() - set(ORTHANC_FRAMEWORK_VERSION "1.4.1") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") -endif() - -set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") -set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") -set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") - -add_definitions( - -DORTHANC_ENABLE_LOGGING_PLUGIN=0 - ) - - -##################################################################### -## Build a static library containing the Orthanc Stone framework -##################################################################### - -LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) - -include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneConfiguration.cmake) - -add_library(OrthancStone STATIC - ${ORTHANC_STONE_SOURCES} - ) - -##################################################################### -## Build all the sample applications -##################################################################### - -include_directories(${ORTHANC_STONE_ROOT}) - -list(APPEND RTVIEWERDEMO_APPLICATION_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleInteractor.h - ${ORTHANC_STONE_ROOT}/Applications/Samples/SampleApplicationBase.h - ) - -if (ENABLE_WASM) - list(APPEND RTVIEWERDEMO_APPLICATION_SOURCES - ${STONE_WASM_SOURCES} - ) -endif() - -add_executable(RtViewerDemo - main.cpp - ${RTVIEWERDEMO_APPLICATION_SOURCES} -) -set_target_properties(RtViewerDemo PROPERTIES COMPILE_DEFINITIONS ORTHANC_STONE_SAMPLE=3) -target_include_directories(RtViewerDemo PRIVATE ${ORTHANC_STONE_ROOT}) -target_link_libraries(RtViewerDemo OrthancStone) - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/build-sdl-msvc15.ps1 --- a/Applications/Samples/rt-viewer-demo/build-sdl-msvc15.ps1 Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -if (-not (Test-Path "build-sdl-msvc15")) { - mkdir -p "build-sdl-msvc15" -} - -cd build-sdl-msvc15 - -cmake -G "Visual Studio 15 2017 Win64" -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DSTONE_SOURCES_DIR="$($pwd)\..\..\..\.." -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\..\..\..\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON .. - -if (!$?) { - Write-Error 'cmake configuration failed' -ErrorAction Stop -} - -cmake --build . --target RtViewerDemo --config Debug - -if (!$?) { - Write-Error 'cmake build failed' -ErrorAction Stop -} - -cd Debug - -.\RtViewerDemo.exe --ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 - - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/build-wasm.sh --- a/Applications/Samples/rt-viewer-demo/build-wasm.sh Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -#!/bin/bash -# -# usage: -# build-wasm BUILD_TYPE -# where BUILD_TYPE is Debug, RelWithDebInfo or Release - -set -e - -buildType=${1:-Debug} - -currentDir=$(pwd) -currentDirAbs=$(realpath $currentDir) - -mkdir -p build-wasm -cd build-wasm - -source ~/apps/emsdk/emsdk_env.sh -cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake \ --DCMAKE_BUILD_TYPE=$buildType -DSTONE_SOURCES_DIR=$currentDirAbs/../../../../orthanc-stone \ --DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=$currentDirAbs/../../../../orthanc \ --DALLOW_DOWNLOADS=ON .. -DENABLE_WASM=ON - -ninja $target - -echo "-- building the web application -- " -cd $currentDir -./build-web.sh - -echo "Launch start-serving-files.sh to access the web sample application locally" diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/build-web.sh --- a/Applications/Samples/rt-viewer-demo/build-web.sh Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -#!/bin/bash - -set -e - -target=${1:-all} -# this script currently assumes that the wasm code has been built on its side and is availabie in build-wasm/ - -currentDir=$(pwd) -samplesRootDir=$(pwd) - -tscOutput=$samplesRootDir/build-tsc-output/ -outputDir=$samplesRootDir/build-web/ -mkdir -p "$outputDir" - -# files used by all single files samples -cp "$samplesRootDir/index.html" "$outputDir" -cp "$samplesRootDir/samples-styles.css" "$outputDir" - -# build rt-viewer-demo -cp $samplesRootDir/rt-viewer-demo.html $outputDir -tsc --project $samplesRootDir/rt-viewer-demo.tsconfig.json --outDir "$tscOutput" -browserify \ - "$tscOutput/orthanc-stone/Platforms/Wasm/logger.js" \ - "$tscOutput/orthanc-stone/Platforms/Wasm/stone-framework-loader.js" \ - "$tscOutput/orthanc-stone/Platforms/Wasm/wasm-application-runner.js" \ - "$tscOutput/orthanc-stone/Platforms/Wasm/wasm-viewport.js" \ - "$tscOutput/rt-viewer-sample/rt-viewer-demo.js" \ - -o "$outputDir/app-rt-viewer-demo.js" -cp "$currentDir/build-wasm/RtViewerDemo.js" $outputDir -cp "$currentDir/build-wasm/RtViewerDemo.wasm" $outputDir - -cd $currentDir diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/index.html --- a/Applications/Samples/rt-viewer-demo/index.html Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ - - - - - - - - - - - - Wasm Samples - - - - - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/main.cpp --- a/Applications/Samples/rt-viewer-demo/main.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,893 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "Applications/IStoneApplication.h" -#include "Framework/Widgets/WorldSceneWidget.h" -#include "Framework/Widgets/LayoutWidget.h" - -#if ORTHANC_ENABLE_WASM==1 - #include "Platforms/Wasm/WasmPlatformApplicationAdapter.h" - #include "Platforms/Wasm/Defaults.h" - #include "Platforms/Wasm/WasmViewport.h" -#endif - -#if ORTHANC_ENABLE_QT==1 - #include "Qt/SampleMainWindow.h" - #include "Qt/SampleMainWindowWithButtons.h" -#endif - -#include "Framework/Layers/DicomSeriesVolumeSlicer.h" -#include "Framework/Widgets/SliceViewerWidget.h" -#include "Framework/Volumes/StructureSetLoader.h" - -#include -#include -#include - -#include -#include "Framework/dev.h" -#include "Framework/Widgets/LayoutWidget.h" -#include "Framework/Layers/DicomStructureSetSlicer.h" - -namespace OrthancStone -{ - namespace Samples - { - class RtViewerDemoBaseApplication : public IStoneApplication - { - protected: - // ownership is transferred to the application context -#ifndef RESTORE_NON_RTVIEWERDEMO_BEHAVIOR - LayoutWidget* mainWidget_; -#else - WorldSceneWidget* mainWidget_; -#endif - - public: - virtual void Initialize(StoneApplicationContext* context, - IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE - { - } - - virtual std::string GetTitle() const ORTHANC_OVERRIDE - { - return "Stone of Orthanc - Sample"; - } - - /** - * In the basic samples, the commands are handled by the platform adapter and NOT - * by the application handler - */ - virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE {}; - - - virtual void Finalize() ORTHANC_OVERRIDE {} - virtual IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainWidget_;} - -#if ORTHANC_ENABLE_WASM==1 - // default implementations for a single canvas named "canvas" in the HTML and an empty WasmApplicationAdapter - - virtual void InitializeWasm() ORTHANC_OVERRIDE - { - AttachWidgetToWasmViewport("canvas", mainWidget_); - } - - virtual WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(MessageBroker& broker) - { - return new WasmPlatformApplicationAdapter(broker, *this); - } -#endif - - }; - - // this application actually works in Qt and WASM - class RtViewerDemoBaseSingleCanvasWithButtonsApplication : public RtViewerDemoBaseApplication - { -public: - virtual void OnPushButton1Clicked() {} - virtual void OnPushButton2Clicked() {} - virtual void OnTool1Clicked() {} - virtual void OnTool2Clicked() {} - - virtual void GetButtonNames(std::string& pushButton1, - std::string& pushButton2, - std::string& tool1, - std::string& tool2 - ) { - pushButton1 = "action1"; - pushButton2 = "action2"; - tool1 = "tool1"; - tool2 = "tool2"; - } - -#if ORTHANC_ENABLE_QT==1 - virtual QStoneMainWindow* CreateQtMainWindow() { - return new SampleMainWindowWithButtons(dynamic_cast(*context_), *this); - } -#endif - - }; - - // this application actually works in SDL and WASM - class RtViewerDemoBaseApplicationSingleCanvas : public RtViewerDemoBaseApplication - { -public: - -#if ORTHANC_ENABLE_QT==1 - virtual QStoneMainWindow* CreateQtMainWindow() { - return new SampleMainWindow(dynamic_cast(*context_), *this); - } -#endif - }; - } -} - - - -namespace OrthancStone -{ - namespace Samples - { - template - void ReadDistributionInternal(std::vector& distribution, - const Orthanc::ImageAccessor& image) - { - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - distribution.resize(width * height); - size_t pos = 0; - - for (unsigned int y = 0; y < height; y++) - { - for (unsigned int x = 0; x < width; x++, pos++) - { - distribution[pos] = Orthanc::ImageTraits::GetFloatPixel(image, x, y); - } - } - } - - void ReadDistribution(std::vector& distribution, - const Orthanc::ImageAccessor& image) - { - switch (image.GetFormat()) - { - case Orthanc::PixelFormat_Grayscale8: - ReadDistributionInternal(distribution, image); - break; - - case Orthanc::PixelFormat_Grayscale16: - ReadDistributionInternal(distribution, image); - break; - - case Orthanc::PixelFormat_SignedGrayscale16: - ReadDistributionInternal(distribution, image); - break; - - case Orthanc::PixelFormat_Grayscale32: - ReadDistributionInternal(distribution, image); - break; - - case Orthanc::PixelFormat_Grayscale64: - ReadDistributionInternal(distribution, image); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - - class DoseInteractor : public VolumeImageInteractor - { - private: - SliceViewerWidget& widget_; - size_t layer_; - DicomFrameConverter converter_; - - - - protected: - virtual void NotifySliceChange(const ISlicedVolume& slicedVolume, - const size_t& sliceIndex, - const Slice& slice) - { - converter_ = slice.GetConverter(); - - #if 0 - const OrthancVolumeImage& volume = dynamic_cast(slicedVolume); - - RenderStyle s = widget_.GetLayerStyle(layer_); - - if (volume.FitWindowingToRange(s, slice.GetConverter())) - { - printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); - widget_.SetLayerStyle(layer_, s); - } - #endif - } - - virtual void NotifyVolumeReady(const ISlicedVolume& slicedVolume) - { - const float percentile = 0.01f; - const OrthancVolumeImage& volume = dynamic_cast(slicedVolume); - - std::vector distribution; - ReadDistribution(distribution, volume.GetImage().GetInternalImage()); - std::sort(distribution.begin(), distribution.end()); - - int start = static_cast(std::ceil(distribution.size() * percentile)); - int end = static_cast(std::floor(distribution.size() * (1.0f - percentile))); - - float a = 0; - float b = 0; - - if (start < end && - start >= 0 && - end < static_cast(distribution.size())) - { - a = distribution[start]; - b = distribution[end]; - } - else if (!distribution.empty()) - { - // Too small distribution: Use full range - a = distribution.front(); - b = distribution.back(); - } - - //printf("%f %f\n", a, b); - - RenderStyle s = widget_.GetLayerStyle(layer_); - s.windowing_ = ImageWindowing_Custom; - s.customWindowCenter_ = static_cast(converter_.Apply((a + b) / 2.0f)); - s.customWindowWidth_ = static_cast(converter_.Apply(b - a)); - - // 96.210556 => 192.421112 - widget_.SetLayerStyle(layer_, s); - printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); - } - - public: - DoseInteractor(MessageBroker& broker, OrthancVolumeImage& volume, - SliceViewerWidget& widget, - VolumeProjection projection, - size_t layer) : - VolumeImageInteractor(broker, volume, widget, projection), - widget_(widget), - layer_(layer) - { - } - }; - - class RtViewerDemoApplication : - public RtViewerDemoBaseApplicationSingleCanvas, - public IObserver - { - public: - std::vector > doseCtWidgetLayerPairs_; - std::list interactors_; - - class Interactor : public IWorldSceneInteractor - { - private: - RtViewerDemoApplication& application_; - - public: - Interactor(RtViewerDemoApplication& application) : - application_(application) - { - } - - virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, - const ViewportGeometry& view, - MouseButton button, - KeyboardModifiers modifiers, - int viewportX, - int viewportY, - double x, - double y, - IStatusBar* statusBar, - const std::vector& displayTouches) - { - return NULL; - } - - virtual void MouseOver(CairoContext& context, - WorldSceneWidget& widget, - const ViewportGeometry& view, - double x, - double y, - IStatusBar* statusBar) - { - if (statusBar != NULL) - { - Vector p = dynamic_cast(widget).GetSlice().MapSliceToWorldCoordinates(x, y); - - char buf[64]; - sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", - p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); - statusBar->SetMessage(buf); - } - } - - virtual void MouseWheel(WorldSceneWidget& widget, - MouseWheelDirection direction, - KeyboardModifiers modifiers, - IStatusBar* statusBar) - { - int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1); - - switch (direction) - { - case MouseWheelDirection_Up: - application_.OffsetSlice(-scale); - break; - - case MouseWheelDirection_Down: - application_.OffsetSlice(scale); - break; - - default: - break; - } - } - - virtual void KeyPressed(WorldSceneWidget& widget, - KeyboardKeys key, - char keyChar, - KeyboardModifiers modifiers, - IStatusBar* statusBar) - { - switch (keyChar) - { - case 's': - // TODO: recursively traverse children - widget.FitContent(); - break; - - default: - break; - } - } - }; - - void OffsetSlice(int offset) - { - if (source_ != NULL) - { - int slice = static_cast(slice_) + offset; - - if (slice < 0) - { - slice = 0; - } - - if (slice >= static_cast(source_->GetSliceCount())) - { - slice = static_cast(source_->GetSliceCount()) - 1; - } - - if (slice != static_cast(slice_)) - { - SetSlice(slice); - } - } - } - - - SliceViewerWidget& GetMainWidget() - { - return *dynamic_cast(mainWidget_); - } - - - void SetSlice(size_t index) - { - if (source_ != NULL && - index < source_->GetSliceCount()) - { - slice_ = static_cast(index); - -#if 1 - GetMainWidget().SetSlice(source_->GetSlice(slice_).GetGeometry()); -#else - // TEST for scene extents - Rotate the axes - double a = 15.0 / 180.0 * boost::math::constants::pi(); - -#if 1 - Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); - Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0); -#else - // Flip the normal - Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); - Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0); -#endif - - SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y); - widget_->SetSlice(s); -#endif - } - } - - - void OnMainWidgetGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message) - { - // Once the geometry of the series is downloaded from Orthanc, - // display its middle slice, and adapt the viewport to fit this - // slice - if (source_ == &message.GetOrigin()) - { - SetSlice(source_->GetSliceCount() / 2); - } - - GetMainWidget().FitContent(); - } - - DicomFrameConverter converter_; - - void OnSliceContentChangedMessage(const ISlicedVolume::SliceContentChangedMessage& message) - { - converter_ = message.GetSlice().GetConverter(); - } - - void OnVolumeReadyMessage(const ISlicedVolume::VolumeReadyMessage& message) - { - const float percentile = 0.01f; - - auto& slicedVolume = message.GetOrigin(); - const OrthancVolumeImage& volume = dynamic_cast(slicedVolume); - - std::vector distribution; - ReadDistribution(distribution, volume.GetImage().GetInternalImage()); - std::sort(distribution.begin(), distribution.end()); - - int start = static_cast(std::ceil(distribution.size() * percentile)); - int end = static_cast(std::floor(distribution.size() * (1.0f - percentile))); - - float a = 0; - float b = 0; - - if (start < end && - start >= 0 && - end < static_cast(distribution.size())) - { - a = distribution[start]; - b = distribution[end]; - } - else if (!distribution.empty()) - { - // Too small distribution: Use full range - a = distribution.front(); - b = distribution.back(); - } - - //printf("WINDOWING %f %f\n", a, b); - - for (const auto& pair : doseCtWidgetLayerPairs_) - { - auto widget = pair.first; - auto layer = pair.second; - RenderStyle s = widget->GetLayerStyle(layer); - s.windowing_ = ImageWindowing_Custom; - s.customWindowCenter_ = static_cast(converter_.Apply((a + b) / 2.0f)); - s.customWindowWidth_ = static_cast(converter_.Apply(b - a)); - - // 96.210556 => 192.421112 - widget->SetLayerStyle(layer, s); - printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); - } - } - - - - size_t AddDoseLayer(SliceViewerWidget& widget, - OrthancVolumeImage& volume, VolumeProjection projection); - - void AddStructLayer( - SliceViewerWidget& widget, StructureSetLoader& loader); - - SliceViewerWidget* CreateDoseCtWidget( - std::unique_ptr& ct, - std::unique_ptr& dose, - std::unique_ptr& structLoader, - VolumeProjection projection); - - void AddCtLayer(SliceViewerWidget& widget, OrthancVolumeImage& volume); - - std::unique_ptr mainWidgetInteractor_; - const DicomSeriesVolumeSlicer* source_; - unsigned int slice_; - - std::string ctSeries_; - std::string doseInstance_; - std::string doseSeries_; - std::string structInstance_; - std::unique_ptr dose_; - std::unique_ptr ct_; - std::unique_ptr struct_; - - public: - RtViewerDemoApplication(MessageBroker& broker) : - IObserver(broker), - source_(NULL), - slice_(0) - { - } - - /* - dev options on bgo xps15 - - COMMAND LINE - --ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 - - URL PARAMETERS - ?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 - - */ - - void ParseParameters(const boost::program_options::variables_map& parameters) - { - // Generic - { - if (parameters.count("verbose")) - { - Orthanc::Logging::EnableInfoLevel(true); - LOG(INFO) << "Verbose logs (info) are enabled"; - } - } - - { - if (parameters.count("trace")) - { - LOG(INFO) << "parameters.count(\"trace\") != 0"; - Orthanc::Logging::EnableTraceLevel(true); - VLOG(1) << "Trace logs (debug) are enabled"; - } - } - - // CT series - { - - if (parameters.count("ct-series") != 1) - { - LOG(ERROR) << "There must be exactly one CT series specified"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - ctSeries_ = parameters["ct-series"].as(); - } - - // RTDOSE - { - if (parameters.count("dose-instance") == 1) - { - doseInstance_ = parameters["dose-instance"].as(); - } - else - { -#ifdef BGO_NOT_IMPLEMENTED_YET - // Dose series - if (parameters.count("dose-series") != 1) - { - LOG(ERROR) << "the RTDOSE series is missing"; - throw Orthanc::OrthancException( - Orthanc::ErrorCode_ParameterOutOfRange); - } - doseSeries_ = parameters["ct"].as(); -#endif - LOG(ERROR) << "the RTSTRUCT instance is missing"; - throw Orthanc::OrthancException( - Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - // RTSTRUCT - { - if (parameters.count("struct-instance") == 1) - { - structInstance_ = parameters["struct-instance"].as(); - } - else - { -#ifdef BGO_NOT_IMPLEMENTED_YET - // Struct series - if (parameters.count("struct-series") != 1) - { - LOG(ERROR) << "the RTSTRUCT series is missing"; - throw Orthanc::OrthancException( - Orthanc::ErrorCode_ParameterOutOfRange); - } - structSeries_ = parameters["struct-series"].as(); -#endif - LOG(ERROR) << "the RTSTRUCT instance is missing"; - throw Orthanc::OrthancException( - Orthanc::ErrorCode_ParameterOutOfRange); - } - } - } - - virtual void DeclareStartupOptions( - boost::program_options::options_description& options) - { - boost::program_options::options_description generic( - "RtViewerDemo options. Please note that some of these options " - "are mutually exclusive"); - generic.add_options() - ("ct-series", boost::program_options::value(), - "Orthanc ID of the CT series") - ("dose-instance", boost::program_options::value(), - "Orthanc ID of the RTDOSE instance (incompatible with dose-series)") - ("dose-series", boost::program_options::value(), - "NOT IMPLEMENTED YET. Orthanc ID of the RTDOSE series (incompatible" - " with dose-instance)") - ("struct-instance", boost::program_options::value(), - "Orthanc ID of the RTSTRUCT instance (incompatible with struct-" - "series)") - ("struct-series", boost::program_options::value(), - "NOT IMPLEMENTED YET. Orthanc ID of the RTSTRUCT (incompatible with" - " struct-instance)") - ("smooth", boost::program_options::value()->default_value(true), - "Enable bilinear image smoothing") - ; - - options.add(generic); - } - - virtual void Initialize( - StoneApplicationContext* context, - IStatusBar& statusBar, - const boost::program_options::variables_map& parameters) - { - using namespace OrthancStone; - - ParseParameters(parameters); - - context_ = context; - - statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); - - if (!ctSeries_.empty()) - { - printf("CT = [%s]\n", ctSeries_.c_str()); - - ct_.reset(new OrthancStone::OrthancVolumeImage( - IObserver::GetBroker(), context->GetOrthancApiClient(), false)); - ct_->ScheduleLoadSeries(ctSeries_); - //ct_->ScheduleLoadSeries( - // "a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // IBA - //ct_->ScheduleLoadSeries( - // "03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); // 0522c0001 TCIA - } - - if (!doseSeries_.empty() || - !doseInstance_.empty()) - { - dose_.reset(new OrthancStone::OrthancVolumeImage( - IObserver::GetBroker(), context->GetOrthancApiClient(), true)); - - - dose_->RegisterObserverCallback( - new Callable - (*this, &RtViewerDemoApplication::OnVolumeReadyMessage)); - - dose_->RegisterObserverCallback( - new Callable - (*this, &RtViewerDemoApplication::OnSliceContentChangedMessage)); - - if (doseInstance_.empty()) - { - dose_->ScheduleLoadSeries(doseSeries_); - } - else - { - dose_->ScheduleLoadInstance(doseInstance_); - } - - //dose_->ScheduleLoadInstance( - //"830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1 - //dose_->ScheduleLoadInstance( - //"269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c"); //0522c0001 TCIA - } - - if (!structInstance_.empty()) - { - struct_.reset(new OrthancStone::StructureSetLoader( - IObserver::GetBroker(), context->GetOrthancApiClient())); - - struct_->ScheduleLoadInstance(structInstance_); - - //struct_->ScheduleLoadInstance( - //"54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // IBA - //struct_->ScheduleLoadInstance( - //"17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA - } - - mainWidget_ = new LayoutWidget("main-layout"); - mainWidget_->SetBackgroundColor(0, 0, 0); - mainWidget_->SetBackgroundCleared(true); - mainWidget_->SetPadding(0); - - auto axialWidget = CreateDoseCtWidget - (ct_, dose_, struct_, OrthancStone::VolumeProjection_Axial); - mainWidget_->AddWidget(axialWidget); - - std::unique_ptr subLayout( - new OrthancStone::LayoutWidget("main-layout")); - subLayout->SetVertical(); - subLayout->SetPadding(5); - - auto coronalWidget = CreateDoseCtWidget - (ct_, dose_, struct_, OrthancStone::VolumeProjection_Coronal); - subLayout->AddWidget(coronalWidget); - - auto sagittalWidget = CreateDoseCtWidget - (ct_, dose_, struct_, OrthancStone::VolumeProjection_Sagittal); - subLayout->AddWidget(sagittalWidget); - - mainWidget_->AddWidget(subLayout.release()); - } - }; - - - size_t RtViewerDemoApplication::AddDoseLayer( - SliceViewerWidget& widget, - OrthancVolumeImage& volume, VolumeProjection projection) - { - size_t layer = widget.AddLayer( - new VolumeImageMPRSlicer(IObserver::GetBroker(), volume)); - - RenderStyle s; - //s.drawGrid_ = true; - s.SetColor(255, 0, 0); // Draw missing PET layer in red - s.alpha_ = 0.3f; - s.applyLut_ = true; - s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; - s.interpolation_ = ImageInterpolation_Bilinear; - widget.SetLayerStyle(layer, s); - - return layer; - } - - void RtViewerDemoApplication::AddStructLayer( - SliceViewerWidget& widget, StructureSetLoader& loader) - { - widget.AddLayer(new DicomStructureSetSlicer( - IObserver::GetBroker(), loader)); - } - - SliceViewerWidget* RtViewerDemoApplication::CreateDoseCtWidget( - std::unique_ptr& ct, - std::unique_ptr& dose, - std::unique_ptr& structLoader, - VolumeProjection projection) - { - std::unique_ptr widget( - new OrthancStone::SliceViewerWidget(IObserver::GetBroker(), - "ct-dose-widget")); - - if (ct.get() != NULL) - { - AddCtLayer(*widget, *ct); - } - - if (dose.get() != NULL) - { - size_t layer = AddDoseLayer(*widget, *dose, projection); - - // we need to store the dose rendering widget because we'll update them - // according to various asynchronous events - doseCtWidgetLayerPairs_.push_back(std::make_pair(widget.get(), layer)); -#if 0 - interactors_.push_back(new VolumeImageInteractor( - IObserver::GetBroker(), *dose, *widget, projection)); -#else - interactors_.push_back(new DoseInteractor( - IObserver::GetBroker(), *dose, *widget, projection, layer)); -#endif - } - else if (ct.get() != NULL) - { - interactors_.push_back( - new VolumeImageInteractor( - IObserver::GetBroker(), *ct, *widget, projection)); - } - - if (structLoader.get() != NULL) - { - AddStructLayer(*widget, *structLoader); - } - - return widget.release(); - } - - void RtViewerDemoApplication::AddCtLayer( - SliceViewerWidget& widget, - OrthancVolumeImage& volume) - { - size_t layer = widget.AddLayer( - new VolumeImageMPRSlicer(IObserver::GetBroker(), volume)); - - RenderStyle s; - //s.drawGrid_ = true; - s.alpha_ = 1; - s.windowing_ = ImageWindowing_Bone; - widget.SetLayerStyle(layer, s); - } - } -} - - - -#if ORTHANC_ENABLE_WASM==1 - -#include "Platforms/Wasm/WasmWebService.h" -#include "Platforms/Wasm/WasmViewport.h" - -#include - -//#include "SampleList.h" - - -OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker) -{ - return new OrthancStone::Samples::RtViewerDemoApplication(broker); -} - -OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, OrthancStone::IStoneApplication* application) -{ - return dynamic_cast(application)->CreateWasmApplicationAdapter(broker); -} - -#else - -//#include "SampleList.h" -#if ORTHANC_ENABLE_SDL==1 -#include "Applications/Sdl/SdlStoneApplicationRunner.h" -#endif -#if ORTHANC_ENABLE_QT==1 -#include "Applications/Qt/SampleQtApplicationRunner.h" -#endif -#include "Framework/Messages/MessageBroker.h" - -int main(int argc, char* argv[]) -{ - OrthancStone::MessageBroker broker; - OrthancStone::Samples::RtViewerDemoApplication sampleStoneApplication(broker); - -#if ORTHANC_ENABLE_SDL==1 - OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(broker, sampleStoneApplication); - return sdlApplicationRunner.Execute(argc, argv); -#endif -#if ORTHANC_ENABLE_QT==1 - OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication); - return qtAppRunner.Execute(argc, argv); -#endif -} - - -#endif - - - - - - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/nginx.local.conf --- a/Applications/Samples/rt-viewer-demo/nginx.local.conf Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -# Local config to serve the WASM samples static files and reverse proxy Orthanc. -# Uses port 9977 instead of 80. - -# `events` section is mandatory -events { - worker_connections 1024; # Default: 1024 -} - -http { - - # prevent nginx sync issues on OSX - proxy_buffering off; - - server { - listen 9977 default_server; - client_max_body_size 4G; - - # location may have to be adjusted depending on your OS and nginx install - include /etc/nginx/mime.types; - # if not in your system mime.types, add this line to support WASM: - # types { - # application/wasm wasm; - # } - - # serve WASM static files - root build-web/; - location / { - } - - # reverse proxy orthanc - location /orthanc/ { - rewrite /orthanc(.*) $1 break; - proxy_pass http://127.0.0.1:8042; - proxy_set_header Host $http_host; - proxy_set_header my-auth-header good-token; - proxy_request_buffering off; - proxy_max_temp_file_size 0; - client_max_body_size 0; - } - - - } - -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/rt-viewer-demo.html --- a/Applications/Samples/rt-viewer-demo/rt-viewer-demo.html Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ - - - - - - - - - - - - Simple Viewer - - - -
-

RTSTRUCT viewer demonstration

-
-
- -
- - - - \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/rt-viewer-demo.ts --- a/Applications/Samples/rt-viewer-demo/rt-viewer-demo.ts Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -import { InitializeWasmApplication } from '../../../Platforms/Wasm/wasm-application-runner'; - - -InitializeWasmApplication("RtViewerDemo", "/orthanc"); - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/rt-viewer-demo.tsconfig.json --- a/Applications/Samples/rt-viewer-demo/rt-viewer-demo.tsconfig.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -{ - "extends" : "./tsconfig-samples", - "compilerOptions": { - }, - "include" : [ - "rt-viewer-demo.ts" - ] -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/samples-styles.css --- a/Applications/Samples/rt-viewer-demo/samples-styles.css Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -html, body { - width: 100%; - height: 100%; - margin: 0px; - border: 0; - overflow: hidden; /* Disable scrollbars */ - display: block; /* No floating content on sides */ - background-color: black; - color: white; - font-family: Arial, Helvetica, sans-serif; -} - -canvas { - left:0px; - top:0px; -} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/start-serving-files.sh --- a/Applications/Samples/rt-viewer-demo/start-serving-files.sh Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -#!/bin/bash - -sudo nginx -p $(pwd) -c nginx.local.conf - -echo "Please browse to :" - -echo "http://localhost:9977/rt-viewer-demo.html?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9" - -echo "(This requires you have uploaded the correct files to your local Orthanc instance)" diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/stop-serving-files.sh --- a/Applications/Samples/rt-viewer-demo/stop-serving-files.sh Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -#!/bin/bash - -sudo nginx -s stop - diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/rt-viewer-demo/tsconfig-samples.json --- a/Applications/Samples/rt-viewer-demo/tsconfig-samples.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -{ - "extends" : "../../../Platforms/Wasm/tsconfig-stone.json", - "compilerOptions": { - "sourceMap": false, - "lib" : [ - "es2017", - "dom", - "dom.iterable" - ] - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Samples/tsconfig-stone.json --- a/Applications/Samples/tsconfig-stone.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -{ - "include" : [ - "../../Platforms/Wasm/stone-framework-loader.ts", - "../../Platforms/Wasm/wasm-application-runner.ts", - "../../Platforms/Wasm/wasm-viewport.ts" - ] -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Sdl/SdlCairoSurface.h --- a/Applications/Sdl/SdlCairoSurface.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Sdl/SdlCairoSurface.h Wed Apr 22 14:05:47 2020 +0200 @@ -29,6 +29,7 @@ #include +#include #include namespace OrthancStone diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Sdl/SdlEngine.cpp --- a/Applications/Sdl/SdlEngine.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Sdl/SdlEngine.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -99,9 +99,7 @@ SdlEngine::SdlEngine(SdlWindow& window, - NativeStoneApplicationContext& context, - MessageBroker& broker) : - IObserver(broker), + NativeStoneApplicationContext& context) : window_(window), context_(context), surface_(window), diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Sdl/SdlEngine.h --- a/Applications/Sdl/SdlEngine.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Sdl/SdlEngine.h Wed Apr 22 14:05:47 2020 +0200 @@ -23,12 +23,13 @@ #if ORTHANC_ENABLE_SDL == 1 +#include "../../Framework/Messages/ObserverBase.h" +#include "../Generic/NativeStoneApplicationContext.h" #include "SdlCairoSurface.h" -#include "../Generic/NativeStoneApplicationContext.h" namespace OrthancStone { - class SdlEngine : public IObserver + class SdlEngine : public ObserverBase { private: SdlWindow& window_; @@ -46,8 +47,7 @@ public: SdlEngine(SdlWindow& window, - NativeStoneApplicationContext& context, - MessageBroker& broker); + NativeStoneApplicationContext& context); void OnViewportChanged(const Deprecated::IViewport::ViewportChangedMessage& message) { diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Sdl/SdlOrthancSurface.cpp --- a/Applications/Sdl/SdlOrthancSurface.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Sdl/SdlOrthancSurface.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -27,6 +27,8 @@ #include #include +#include + namespace OrthancStone { SdlOrthancSurface::SdlOrthancSurface(SdlWindow& window) : diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Sdl/SdlOrthancSurface.h diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Sdl/SdlStoneApplicationRunner.cpp --- a/Applications/Sdl/SdlStoneApplicationRunner.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Sdl/SdlStoneApplicationRunner.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -103,20 +103,19 @@ LOG(WARNING) << "Starting the application"; SdlWindow window(title.c_str(), width_, height_, enableOpenGl_); - SdlEngine sdl(window, context, broker_); + boost::shared_ptr sdl(new SdlEngine(window, context)); { NativeStoneApplicationContext::GlobalMutexLocker locker(context); - locker.GetCentralViewport().RegisterObserverCallback( - new Callable - (sdl, &SdlEngine::OnViewportChanged)); + sdl->Register + (locker.GetCentralViewport(), &SdlEngine::OnViewportChanged); //context.GetCentralViewport().Register(sdl); // (*) } context.Start(); - sdl.Run(); + sdl->Run(); LOG(WARNING) << "Stopping the application"; diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/Sdl/SdlStoneApplicationRunner.h --- a/Applications/Sdl/SdlStoneApplicationRunner.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/Sdl/SdlStoneApplicationRunner.h Wed Apr 22 14:05:47 2020 +0200 @@ -39,9 +39,8 @@ bool enableOpenGl_; public: - SdlStoneApplicationRunner(MessageBroker& broker, - IStoneApplication& application) : - NativeStoneApplicationRunner(broker, application) + SdlStoneApplicationRunner(boost::shared_ptr application) : + NativeStoneApplicationRunner(application) { } diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/StoneApplicationContext.cpp --- a/Applications/StoneApplicationContext.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/StoneApplicationContext.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -32,35 +32,35 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - orthanc_.reset(new Deprecated::OrthancApiClient(broker_, *webService_, orthancBaseUrl_)); + orthanc_.reset(new Deprecated::OrthancApiClient(*webService_, orthancBaseUrl_)); } - Deprecated::IWebService& StoneApplicationContext::GetWebService() + boost::shared_ptr StoneApplicationContext::GetWebService() { if (webService_ == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - return *webService_; + return webService_; } - Deprecated::OrthancApiClient& StoneApplicationContext::GetOrthancApiClient() + boost::shared_ptr StoneApplicationContext::GetOrthancApiClient() { if (orthanc_.get() == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - return *orthanc_; + return orthanc_; } - void StoneApplicationContext::SetWebService(Deprecated::IWebService& webService) + void StoneApplicationContext::SetWebService(boost::shared_ptr webService) { - webService_ = &webService; + webService_ = webService; InitializeOrthanc(); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Applications/StoneApplicationContext.h --- a/Applications/StoneApplicationContext.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Applications/StoneApplicationContext.h Wed Apr 22 14:05:47 2020 +0200 @@ -59,18 +59,15 @@ class StoneApplicationContext : public boost::noncopyable { private: - MessageBroker& broker_; - Deprecated::IWebService* webService_; - Deprecated::IDelayedCallExecutor* delayedCallExecutor_; - std::unique_ptr orthanc_; + boost::shared_ptr webService_; + Deprecated::IDelayedCallExecutor* delayedCallExecutor_; // TODO => shared_ptr ?? + boost::shared_ptr orthanc_; std::string orthancBaseUrl_; void InitializeOrthanc(); public: - StoneApplicationContext(MessageBroker& broker) : - broker_(broker), - webService_(NULL), + StoneApplicationContext() : delayedCallExecutor_(NULL) { } @@ -79,21 +76,11 @@ { } - MessageBroker& GetMessageBroker() - { - return broker_; - } + boost::shared_ptr GetWebService(); - bool HasWebService() const - { - return webService_ != NULL; - } + boost::shared_ptr GetOrthancApiClient(); - Deprecated::IWebService& GetWebService(); - - Deprecated::OrthancApiClient& GetOrthancApiClient(); - - void SetWebService(Deprecated::IWebService& webService); + void SetWebService(boost::shared_ptr webService); void SetOrthancBaseUrl(const std::string& baseUrl); diff -r 4431ffdcc2a4 -r f4a06ad1580b Docs/stone-object-model-reference.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Docs/stone-object-model-reference.md Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,429 @@ +## Scene2D and viewport-related object reference + +### `Scene2D` + +Represents a collection of layers that display 2D data. + +These layers must implement `ISceneLayer` + +The layers must be created externally and set to a specific Z-order index +with the `SetLayer` method. + +The `Scene2D` object merely acts as a layer container. It has no rendering +or layer creation facility on its own. + +The `Scene2D` contains an `AffineTransform2D` structure that defines how +the various layer item coordinates are transformed before being displayed +on the viewport (aka canvas) + +It is up to each layer type-specific renderer to choose how this transformation +is used. See the various kinds of layer below for more details. + +Examining the `Scene2D` contents can be done either by implementing the +`Scene2D::IVisitor` interface and calling `Apply(IVisitor& visitor)` or by +iterating between `GetMinDepth()` and `GetMaxDepth()` and calling the +`ISceneLayer& GetLayer(int depth)` getter. + +### `ISceneLayer` + +Interface that must be implemented by `Scene2D` layers. This is a closed list +that, as of 2020-03, contains: + +``` + Type_InfoPanel, + Type_ColorTexture, + Type_Polyline, + Type_Text, + Type_FloatTexture, + Type_LookupTableTexture +``` + +Please note that this interface mandates the implementation of a `GetRevision` +method returning an `uint64_t`. + +The idea is that when a model gets converted to a set of `ISceneLayer` +instances, changes in the model that result in changes to the layers must +increase the revision number of these layers. + +That allows the rendering process to safely assume that a given layers whose +revision does not change hasn't been modified (this helps with caching). + +Every mutable method in `ISceneLayer` instances that possibly change the visual +representation of an `ISceneLayer` must increase this revision number. + +### Implementation: `FloatTextureSceneLayer` + +Layer that renders an `Orthanc::ImageAccessor` object that must be convertible +to `Float32` image. + +The constructor only uses the image accessor to perform a copy. It can safely +be deleted afterwards. + +The input values are mapped to the output values by taking into account various +properties that can be modified with: + +- `SetWindowing`: uses windowing presets like "bone" or "lung" +- `SetCustomWindowing`: with manual window center and width +- `SetInverted`: toggles black <-> white inversion after windowing +- `SetApplyLog`: uses a non-linear response curve described in + https://theailearner.com/2019/01/01/log-transformation/ that expands contrast + in dark areas while compressing contrast in bright ones. This is **not** + implemented in the OpenGL renderer! + +The corresponding renderers are `OpenGLFloatTextureRenderer` and +`CairoFloatTextureRenderer`. The scene transformation is applied during +rendering. + +### Implementation: `ColorTextureSceneLayer` + +Layer that renders an `Orthanc::ImageAccessor` object an RGBA image (alpha must +be premultiplied). + +The constructor only uses the image accessor to perform a copy. It can safely +be deleted afterwards. + +The corresponding renderers are `OpenGLColorTextureRenderer` and +`CairoColorTextureRenderer`. The scene transformation is applied during +rendering. + +### Implementation: `LookupTableTextureSceneLayer` + +Layer that renders an `Orthanc::ImageAccessor` object that must be convertible +to `Float32` image. + +The constructor only uses the image accessor to perform a copy. It can safely +be deleted afterwards. + +The final on-screen color of each pixel is determined by passing the input +`Float32` value through a 256-entry look-up table (LUT) that can be passed as +an array of either 256 x 3 bytes (for opaque RGB colors) or 256 x 4 bytes (for +RGBA pixels). The LUT is not specified at construction time, but with +calls to `SetLookupTable` or `SetLookupTableGrayscale` (that fills the LUT +with a gradient from black to white, fully opaque) + +The range of input values that is mapped to the entirety of the LUT is, by +default, the full image range, but can be customized with `SetRange`. + +The corresponding renderers are `OpenGLLookupTableTextureRenderer` and +`CairoLookupTableTextureRenderer`. The scene transformation is applied during +rendering. + +### Implementation: `PolylineSceneLayer` + +Layer that renders vector-based polygonal lines. + +Polylines can be added with the `AddChain` method, that accepts a `Chain`, that +is a typedef to `std::vector`, a flag to specify whether the +chain must be automatically close (last point of the vector connected to the +first one) and the chain color (a `Color` structure). + +Please note that the line thickness is, contrary to the color, specified +per-chain but rather per-layer. + +If you need multiple line thicknesses, multiple `PolylineSceneLayer` must be +created. + +The corresponding renderers are `OpenGLAdvancedPolylineRenderer` and +`CairoPolylineRenderer`. The scene transformation is applied during +rendering. + +### Implementation: `TextSceneLayer` + +This layers renders a paragraph of text. + +The inputs to the layer can be changed after creation and are: +- The text iself, supplied as an UTF-8 encoded string in `SetText` +- The font used for rendering, set by `SetFontIndex`. +- The text anchoring, through `SetAnchor`: the text can be anchored to + various positions, such as top lef, center, bottom center,... These + various anchors are part of the `BitmapAnchor` enumeration. +- The text position, relative to its anchor, through `SetPosition`. + +The font is supplied as an index. This is an index in the set of fonts +that has been registered in the viewport compositor. The following code +shows how to set such a font: + +``` +std::unique_ptr lock(viewport_.Lock()); +lock->GetCompositor().SetFont(0, + Orthanc::EmbeddedResources::UBUNTU_FONT, + 32, Orthanc::Encoding_Latin1); +// where 32 is the font size in pixels +``` + +This call uses the embedded `UBUNTU_FONT` resource that has been defined in +the `CMakeLists.txt` file with: + +``` +set(ORTHANC_STONE_APPLICATION_RESOURCES + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf +) +``` + +Please note that you must supply a font: there is no default font provided by +the OpenGL or Cairo compositors. + +The corresponding renderers are `OpenGLTextRenderer` and +`CairoTextRenderer`. The scene transformation is not applied during rendering, +because the text anchoring, position and scaling are computed relative to the +viewport/canvas. + +### Implementation: `InfoPanelSceneLayer` + +This layer is designed to display an image, supplied through an +`Orthanc::ImageAccessor` reference (only used at construction time). + +The image is not transformed according to the normal layer transformation but +is rather positioned relative to the canvas, with the same mechanism as the +`TextSceneLayer` described above. + +The image position is specified with the sole means of the `SetAnchor` method. + +The corresponding renderers are `OpenGLInfoPanelRenderer` and +`CairoInfoPanelRenderer`. + +### `IViewport` + +https://bitbucket.org/sjodogne/orthanc-stone/src/broker/Framework/Viewport/IViewport.h + +(**not** the one in `Deprecated`) +- Implemented by classes that: + - manage the on-screen display of a `Scene2D` trough a compositor. + - Own the `ICompositor` object that performs the rendering. + - Own the `Scene2D` (TODO: currently through `ViewportController` --> `Scene2D`) + - Provide a `Lock` method that returns a RAII, that must be kept alive when + modifying the underlying objects (controller, compositor, scene), but not + longer. + +#### Implementation: `SdlOpenGLViewport` +- Implementation of a viewport rendered on a SDL window, that uses OpenGL for + rendering. +- Instantiating this object creates an SDL window. Automatic scaling for hiDPI + displays can be toggled on or off. + +#### Implementation: `WebGLViewport` +- Implementation of a viewport rendered on a DOM canvas, that uses OpenGL for + rendering. +- Contrary to the SDL OpenGL viewport, the canvas must already be existing + when the ctor is called. + +### `ICompositor` +The interface providing a rendering service for `Scene2D` objects. + +**Subclasses:** `CairoCompositor`, `OpenGLCompositor` + +You do not need to create compositor instances. They are created for you when +instantiating a viewport. + +### `ViewportController` +This concrete class is instantiated by its `IViewport` owner. + +**TODO:** its functionality is not well defined and should be moved into the +viewport base class. Support for measuring tools should be moved to a special +interactor. + +- contains: + - array of `MeasureTool` + - ref to `IViewport` + - `activeTracker_` + - owns a `Scene2D` + - weak ref to `UndoStack` + - cached `canvasToSceneFactor_` + +- contains logic to: + - pass commands to undostack (trivial) + - logic to locate `MeasureTool` in the HitTest + - OTOH, the meat of the measuring tool logic (highlighting etc..) is + done in app-specific code (`VolumeSlicerWidget`) + - accept new Scene transform and notify listeners + - **the code that uses the interactor** (`HandleMousePress`) is only + called by the new `WebAssemblyViewport` !!! **TODO** clean this mess + +### `IViewportInteractor` +- must provide logic to respond to `CreateTracker` + +### `DefaultViewportInteractor` +- provides Pan+Rotate+Zoom trackers + +### `WebGLViewportsRegistry` + +This class is a singleton (accessible through `GetWebGLViewportsRegistry()` +that deals with context losses in the WebGL contexts. + +You use it by creating a WebGLViewport in the following fashion: + +``` +boost::shared_ptr viewport( + OrthancStone::GetWebGLViewportsRegistry().Add(canvasId)); +``` + +## Source data related + +### `IVolumeSlicer` + +A very simple interface with a single method: +`IVolumeSlicer::IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)` + +### `IVolumeSlicer::IExtractedSlice` + +On a slice has been extracted from a volume by an `IVolumeSlicer`, it can +report its *revision number*. + +If another call to `ExtractSlice` with the same cutting plane is made, but +the returned slice revision is different, it means that the volume has +changed and the scene layer must be refreshed. + +Please see `VolumeSceneLayerSource::Update` to check how this logic is +implemented. + + +### `OrthancSeriesVolumeProgressiveLoader` + +This class implements `IVolumeSlicer` (and `IObservable`) and can be used to +load a volume stored in a Dicom series on an Orthanc server. + +Over the course of the series loading, various notifications are sent: + +The first one is `OrthancStone::DicomVolumeImage::GeometryReadyMessage` that +is sent when the volume extent and geometric properties are known. + +Then, as slices get loaded and the volume is filled, +`OrthancStone::DicomVolumeImage::ContentUpdatedMessage` are sent. + +Once all the highest-quality slices have been loaded, the +`OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality` +notification is sent. + +Please note that calling `ExtractSlice` *before* the geometry is loaded will +yield an instance of `InvalidSlice` that cannot be used to create a layer. + +On the other hand, + +### `VolumeSceneLayerSource` + +This class makes the bridge between a volume (supplied by an `IVolumeSlicer` +interface) and a `Scene2D`. + +Please note that the bulk of the work is done the objects implementing +`IVolumeSlicer` and this object merely connects things together. + +For instance, deciding whether an image (texture) or vector (polyline) layer +is done by the `IVolumeSlicer` implementation. + +- contains: + - reference to Scene2D + - `layerIndex_` (fixed at ctor) that is the index, in the Scene2D layer + stack, of the layer that will be created/updated + - `IVolumeSlicer` + +- contains logic to: + - extract a slice from the slicer and set/refresh the Scene2D layer at + the supplied `layerIndex_` + - refresh this based on the slice revision or configuration revision + - accept a configuration that will be applied to the layer + - the `Update()` method will + +## Updates and the configurators + +`ISceneLayer` does not expose mutable methods. + +The way to change a layer once it has been created is through configurator +objets. + +If you plan to set (even only once) or modify some layer properties after +layer creation, you need to create a matching configurator objet. + +For instance, in the `VolumeSceneLayerSource`, the `SetConfigurator` method +will store a `ILayerStyleConfigurator* configurator_`. + +In the `OrthancView` ctor, you can see how it is used: + +``` +std::unique_ptr style( + new GrayscaleStyleConfigurator); + +style->SetLinearInterpolation(true); + +...... + +std::unique_ptr config( + new LookupTableStyleConfigurator); + +config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); + +``` + +The configurator type are created according to the type of layer.¸ + +Later, in `VolumeSceneLayerSource::Update(const CoordinateSystem3D& plane)`, +if the cutting plane has **not** changed and if the layer revision has **not** +changed, we test `configurator_->GetRevision() != lastConfiguratorRevision_` +and, if different, we call `configurator_->ApplyStyle(scene_.GetLayer(layerDepth_));` + +This allows to change layer properties that do not depend on the layer model +contents. + +On the other hand, if the layer revision has changed, when compared to the +last time it has been rendered (stored in `lastRevision_`), then we need to +ask the slice to create a brand new layer. + +Another way to see it is that layer rendering depend on model data and view +data. The model data is not mutable in the layer and, if the model changes, the +layer must be recreated. + +If only the view properties change (the configurator), we call ApplyStyle +(that **will** mutate some of the layer internals) + +Please note that the renderer does **not** know about the configurator : the +renderer uses properies in the layer and does not care whether those have +been set once at construction time or at every frame (configuration time). + + +## Cookbook + +### Simple application + +#### Building + +In order to create a Stone application, you need to: + +- CMake-based application: + ``` + include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneConfiguration.cmake) + ``` + with this library target that you have to define: + ``` + add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES}) + ``` + then link with this library: + ``` + target_link_libraries(MyStoneApplication OrthancStone) + ``` + +Building is supported with emscripten, Visual C++ (>= 9.0), gcc... + +emscripten recommended version >= 1.38.41 + +These are very rough guidelines. See the `Samples` folder for actual examples. + +#### Structure + +The code requires a loader (object that ) + +Initialize: + +``` +Orthanc::Logging::Initialize(); +Orthanc::Logging::EnableInfoLevel(true); +``` +Call, in WASM: +``` +DISPATCH_JAVASCRIPT_EVENT("StoneInitialized"); +``` + +# Notes + +- It is NOT possible to abandon the existing loaders : they contain too much loader-specific getters + + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp --- a/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -87,59 +87,72 @@ } - DicomSeriesVolumeSlicer::DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc) : - IVolumeSlicer(broker), - IObserver(broker), - loader_(broker, orthanc), + DicomSeriesVolumeSlicer::DicomSeriesVolumeSlicer() : quality_(SliceImageQuality_FullPng) { - loader_.RegisterObserverCallback( - new OrthancStone::Callable - (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable - (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable - (*this, &DicomSeriesVolumeSlicer::OnSliceImageReady)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable - (*this, &DicomSeriesVolumeSlicer::OnSliceImageError)); } + void DicomSeriesVolumeSlicer::Connect(boost::shared_ptr orthanc) + { + loader_.reset(new OrthancSlicesLoader(orthanc)); + Register(*loader_, &DicomSeriesVolumeSlicer::OnSliceGeometryReady); + Register(*loader_, &DicomSeriesVolumeSlicer::OnSliceGeometryError); + Register(*loader_, &DicomSeriesVolumeSlicer::OnSliceImageReady); + Register(*loader_, &DicomSeriesVolumeSlicer::OnSliceImageError); + } void DicomSeriesVolumeSlicer::LoadSeries(const std::string& seriesId) { - loader_.ScheduleLoadSeries(seriesId); + if (loader_.get() == NULL) + { + // Should have called "Connect()" + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + loader_->ScheduleLoadSeries(seriesId); } void DicomSeriesVolumeSlicer::LoadInstance(const std::string& instanceId) { - loader_.ScheduleLoadInstance(instanceId); + if (loader_.get() == NULL) + { + // Should have called "Connect()" + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + loader_->ScheduleLoadInstance(instanceId); } void DicomSeriesVolumeSlicer::LoadFrame(const std::string& instanceId, unsigned int frame) { - loader_.ScheduleLoadFrame(instanceId, frame); + if (loader_.get() == NULL) + { + // Should have called "Connect()" + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + loader_->ScheduleLoadFrame(instanceId, frame); } bool DicomSeriesVolumeSlicer::GetExtent(std::vector& points, const OrthancStone::CoordinateSystem3D& viewportSlice) { + if (loader_.get() == NULL) + { + // Should have called "Connect()" + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + size_t index; - if (loader_.IsGeometryReady() && - loader_.LookupSlice(index, viewportSlice)) + if (loader_->IsGeometryReady() && + loader_->LookupSlice(index, viewportSlice)) { - loader_.GetSlice(index).GetExtent(points); + loader_->GetSlice(index).GetExtent(points); return true; } else @@ -151,12 +164,18 @@ void DicomSeriesVolumeSlicer::ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) { + if (loader_.get() == NULL) + { + // Should have called "Connect()" + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + size_t index; - if (loader_.IsGeometryReady() && - loader_.LookupSlice(index, viewportSlice)) + if (loader_->IsGeometryReady() && + loader_->LookupSlice(index, viewportSlice)) { - loader_.ScheduleLoadSliceImage(index, quality_); + loader_->ScheduleLoadSliceImage(index, quality_); } } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h --- a/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,6 +22,7 @@ #pragma once #include "IVolumeSlicer.h" +#include "../../Messages/ObserverBase.h" #include "../Toolbox/IWebService.h" #include "../Toolbox/OrthancSlicesLoader.h" #include "../Toolbox/OrthancApiClient.h" @@ -33,7 +34,7 @@ // messages are sent to observers so they can use it class DicomSeriesVolumeSlicer : public IVolumeSlicer, - public OrthancStone::IObserver + public OrthancStone::ObserverBase //private OrthancSlicesLoader::ISliceLoaderObserver { public: @@ -79,13 +80,14 @@ private: class RendererFactory; - OrthancSlicesLoader loader_; + boost::shared_ptr loader_; SliceImageQuality quality_; public: - DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc); + DicomSeriesVolumeSlicer(); + void Connect(boost::shared_ptr orthanc); + void LoadSeries(const std::string& seriesId); void LoadInstance(const std::string& instanceId); @@ -105,12 +107,12 @@ size_t GetSlicesCount() const { - return loader_.GetSlicesCount(); + return loader_->GetSlicesCount(); } const Slice& GetSlice(size_t slice) const { - return loader_.GetSlice(slice); + return loader_->GetSlice(slice); } virtual bool GetExtent(std::vector& points, diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp --- a/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -20,6 +20,8 @@ #include "DicomStructureSetSlicer.h" +#include "../../Toolbox/DicomStructureSet.h" + namespace Deprecated { class DicomStructureSetSlicer::Renderer : public ILayerRenderer @@ -28,13 +30,18 @@ class Structure { private: - bool visible_; - uint8_t red_; - uint8_t green_; - uint8_t blue_; - std::string name_; + bool visible_; + uint8_t red_; + uint8_t green_; + uint8_t blue_; + std::string name_; + +#if USE_BOOST_UNION_FOR_POLYGONS == 1 + std::vector< std::vector > polygons_; +#else std::vector< std::pair > segments_; - +#endif + public: Structure(OrthancStone::DicomStructureSet& structureSet, const OrthancStone::CoordinateSystem3D& plane, @@ -42,7 +49,12 @@ name_(structureSet.GetStructureName(index)) { structureSet.GetStructureColor(red_, green_, blue_, index); + +#if USE_BOOST_UNION_FOR_POLYGONS == 1 + visible_ = structureSet.ProjectStructure(polygons_, index, plane); +#else visible_ = structureSet.ProjectStructure(segments_, index, plane); +#endif } void Render(OrthancStone::CairoContext& context) @@ -53,12 +65,25 @@ context.SetSourceColor(red_, green_, blue_); +#if USE_BOOST_UNION_FOR_POLYGONS == 1 + for (size_t i = 0; i < polygons_.size(); i++) + { + cairo_move_to(cr, polygons_[i][0].x, polygons_[i][0].y); + for (size_t j = 0; j < polygons_[i].size(); j++) + { + cairo_line_to(cr, polygons_[i][j].x, polygons_[i][j].y); + } + cairo_line_to(cr, polygons_[i][0].x, polygons_[i][0].y); + cairo_stroke(cr); + } +#else for (size_t i = 0; i < segments_.size(); i++) { cairo_move_to(cr, segments_[i].first.x, segments_[i].first.y); - cairo_move_to(cr, segments_[i].second.x, segments_[i].second.y); + cairo_line_to(cr, segments_[i].second.x, segments_[i].second.y); cairo_stroke(cr); } +#endif } } }; @@ -140,15 +165,10 @@ }; - DicomStructureSetSlicer::DicomStructureSetSlicer(OrthancStone::MessageBroker& broker, - StructureSetLoader& loader) : - IVolumeSlicer(broker), - IObserver(broker), + DicomStructureSetSlicer::DicomStructureSetSlicer(StructureSetLoader& loader) : loader_(loader) { - loader_.RegisterObserverCallback( - new OrthancStone::Callable - (*this, &DicomStructureSetSlicer::OnStructureSetLoaded)); + Register(loader_, &DicomStructureSetSlicer::OnStructureSetLoaded); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Layers/DicomStructureSetSlicer.h --- a/Framework/Deprecated/Layers/DicomStructureSetSlicer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.h Wed Apr 22 14:05:47 2020 +0200 @@ -28,7 +28,7 @@ { class DicomStructureSetSlicer : public IVolumeSlicer, - public OrthancStone::IObserver + public OrthancStone::ObserverBase { private: class Renderer; @@ -42,8 +42,7 @@ } public: - DicomStructureSetSlicer(OrthancStone::MessageBroker& broker, - StructureSetLoader& loader); + DicomStructureSetSlicer(StructureSetLoader& loader); virtual bool GetExtent(std::vector& points, const OrthancStone::CoordinateSystem3D& viewportPlane) diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Layers/IVolumeSlicer.h --- a/Framework/Deprecated/Layers/IVolumeSlicer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Layers/IVolumeSlicer.h Wed Apr 22 14:05:47 2020 +0200 @@ -122,11 +122,6 @@ }; - IVolumeSlicer(OrthancStone::MessageBroker& broker) : - IObservable(broker) - { - } - virtual ~IVolumeSlicer() { } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Loaders/DicomStructureSetLoader2.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/DicomStructureSetLoader2.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,125 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "DicomStructureSetLoader2.h" + +#include "../Messages/IObservable.h" +#include "../Oracle/IOracle.h" +#include "../Oracle/OracleCommandExceptionMessage.h" + +namespace Deprecated +{ + + DicomStructureSetLoader2::DicomStructureSetLoader2( + DicomStructureSet2& structureSet + , IOracle& oracle + , IObservable& oracleObservable) + : IObserver(oracleObservable.GetBroker()) + , IObservable(oracleObservable.GetBroker()) + , structureSet_(structureSet) + , oracle_(oracle) + , oracleObservable_(oracleObservable) + , structuresReady_(false) + { + LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::DicomStructureSetLoader2()"; + + oracleObservable.RegisterObserverCallback( + new Callable + (*this, &DicomStructureSetLoader2::HandleSuccessMessage)); + + oracleObservable.RegisterObserverCallback( + new Callable + (*this, &DicomStructureSetLoader2::HandleExceptionMessage)); + } + + DicomStructureSetLoader2::~DicomStructureSetLoader2() + { + LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::~DicomStructureSetLoader2()"; + oracleObservable_.Unregister(this); + } + + void DicomStructureSetLoader2::LoadInstanceFromString(const std::string& body) + { + OrthancPlugins::FullOrthancDataset dicom(body); + //loader.content_.reset(new DicomStructureSet(dicom)); + structureSet_.Clear(); + structureSet_.SetContents(dicom); + SetStructuresReady(); + } + + void DicomStructureSetLoader2::HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message) + { + const std::string& body = message.GetAnswer(); + LoadInstanceFromString(body); + } + + void DicomStructureSetLoader2::HandleExceptionMessage(const OracleCommandExceptionMessage& message) + { + LOG(ERROR) << "DicomStructureSetLoader2::HandleExceptionMessage: error when trying to load data. " + << "Error: " << message.GetException().What() << " Details: " + << message.GetException().GetDetails(); + } + + void DicomStructureSetLoader2::LoadInstance(const std::string& instanceId) + { + std::unique_ptr command(new OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + + std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; + + command->SetUri(uri); + oracle_.Schedule(*this, command.release()); + } + + void DicomStructureSetLoader2::SetStructuresReady() + { + structuresReady_ = true; + } + + bool DicomStructureSetLoader2::AreStructuresReady() const + { + return structuresReady_; + } + + /* + + void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message) + { + LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing"; + LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " << + message.GetException().GetDetails(); + Clear(); + } + + LoaderStateMachine::~LoaderStateMachine() + { + Clear(); + } + + + */ + +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Loaders/DicomStructureSetLoader2.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Loaders/DicomStructureSetLoader2.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,87 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + +#include "../Toolbox/DicomStructureSet2.h" +#include "../Messages/IMessage.h" +#include "../Messages/IObserver.h" +#include "../Messages/IObservable.h" +#include "../Oracle/OrthancRestApiCommand.h" + +#include + +namespace Deprecated +{ + class IOracle; + class IObservable; + class OrthancRestApiCommand; + class OracleCommandExceptionMessage; + + class DicomStructureSetLoader2 : public IObserver, public IObservable + { + public: + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader2); + + /** + Warning: the structureSet, oracle and oracleObservable objects must live + at least as long as this object (TODO: shared_ptr?) + */ + DicomStructureSetLoader2(DicomStructureSet2& structureSet, IOracle& oracle, IObservable& oracleObservable); + + ~DicomStructureSetLoader2(); + + void LoadInstance(const std::string& instanceId); + + /** Internal use */ + void LoadInstanceFromString(const std::string& body); + + void SetStructuresReady(); + bool AreStructuresReady() const; + + private: + /** + Called back by the oracle when data is ready! + */ + void HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message); + + /** + Called back by the oracle when shit hits the fan + */ + void HandleExceptionMessage(const OracleCommandExceptionMessage& message); + + /** + The structure set that will be (cleared and) filled with data from the + loader + */ + DicomStructureSet2& structureSet_; + + IOracle& oracle_; + IObservable& oracleObservable_; + bool structuresReady_; + }; +} + +#endif +// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Messages/LockingEmitter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Messages/LockingEmitter.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "LockingEmitter.h" + +#include + +namespace Deprecated +{ + void LockingEmitter::EmitMessage(boost::weak_ptr observer, + const OrthancStone::IMessage& message) + { + try + { + boost::unique_lock lock(mutex_); + oracleObservable_.EmitMessage(observer, message); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while emitting a message: " << e.What(); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Messages/LockingEmitter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Messages/LockingEmitter.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,89 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include +#include + +#include "../../Messages/IMessageEmitter.h" +#include "../../Messages/IObservable.h" + +#include // For ORTHANC_OVERRIDE + +#include + +namespace Deprecated +{ + /** + * 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 OrthancStone::IMessageEmitter + { + private: + boost::shared_mutex mutex_; + OrthancStone::IObservable oracleObservable_; + + public: + virtual void EmitMessage(boost::weak_ptr observer, + const OrthancStone::IMessage& message) ORTHANC_OVERRIDE; + + + class ReaderLock : public boost::noncopyable + { + private: + LockingEmitter& that_; + boost::shared_lock lock_; + + public: + ReaderLock(LockingEmitter& that) : + that_(that), + lock_(that.mutex_) + { + } + }; + + + class WriterLock : public boost::noncopyable + { + private: + LockingEmitter& that_; + boost::unique_lock lock_; + + public: + WriterLock(LockingEmitter& that) : + that_(that), + lock_(that.mutex_) + { + } + + OrthancStone::IObservable& GetOracleObservable() + { + return that_.oracleObservable_; + } + }; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/SmartLoader.cpp --- a/Framework/Deprecated/SmartLoader.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/SmartLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,7 +21,6 @@ #include "SmartLoader.h" -#include "../Messages/MessageForwarder.h" #include "../StoneException.h" #include "Core/Images/Image.h" #include "Core/Logging.h" @@ -68,11 +67,6 @@ CachedSliceStatus status_; public: - CachedSlice(OrthancStone::MessageBroker& broker) : - IVolumeSlicer(broker) - { - } - virtual ~CachedSlice() { } @@ -106,7 +100,7 @@ CachedSlice* Clone() const { - CachedSlice* output = new CachedSlice(GetBroker()); + CachedSlice* output = new CachedSlice; output->sliceIndex_ = sliceIndex_; output->slice_.reset(slice_->Clone()); output->image_ = image_; @@ -119,10 +113,7 @@ }; - SmartLoader::SmartLoader(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthancApiClient) : - IObservable(broker), - IObserver(broker), + SmartLoader::SmartLoader(boost::shared_ptr orthancApiClient) : imageQuality_(SliceImageQuality_FullPam), orthancApiClient_(orthancApiClient) { @@ -140,7 +131,7 @@ // the messages to its observables // in both cases, we must be carefull about objects lifecycle !!! - std::unique_ptr layerSource; + boost::shared_ptr layerSource; std::string sliceKeyId = instanceId + ":" + boost::lexical_cast(frame); SmartLoader::CachedSlice* cachedSlice = NULL; @@ -151,22 +142,23 @@ } else { - layerSource.reset(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); + layerSource.reset(new DicomSeriesVolumeSlicer); + dynamic_cast(layerSource.get())->Connect(orthancApiClient_); dynamic_cast(layerSource.get())->SetImageQuality(imageQuality_); - layerSource->RegisterObserverCallback(new OrthancStone::Callable(*this, &SmartLoader::OnLayerGeometryReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable(*this, &SmartLoader::OnFrameReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable(*this, &SmartLoader::OnLayerReady)); + Register(*layerSource, &SmartLoader::OnLayerGeometryReady); + Register(*layerSource, &SmartLoader::OnFrameReady); + Register(*layerSource, &SmartLoader::OnLayerReady); dynamic_cast(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()); + sliceViewer.AddLayer(layerSource); } else if (sliceViewer.GetLayerCount() > layerIndex) { - sliceViewer.ReplaceLayer(layerIndex, layerSource.release()); + sliceViewer.ReplaceLayer(layerIndex, layerSource); } else { @@ -190,7 +182,7 @@ // create the slice in the cache with "empty" data - boost::shared_ptr cachedSlice(new CachedSlice(IObserver::GetBroker())); + boost::shared_ptr cachedSlice(new CachedSlice); cachedSlice->slice_.reset(new Slice(instanceId, frame)); cachedSlice->status_ = CachedSliceStatus_ScheduledToLoad; std::string sliceKeyId = instanceId + ":" + boost::lexical_cast(frame); @@ -199,12 +191,12 @@ cachedSlices_[sliceKeyId] = boost::shared_ptr(cachedSlice); - std::unique_ptr layerSource(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); - + std::unique_ptr layerSource(new DicomSeriesVolumeSlicer); + dynamic_cast(layerSource.get())->Connect(orthancApiClient_); dynamic_cast(layerSource.get())->SetImageQuality(imageQuality_); - layerSource->RegisterObserverCallback(new OrthancStone::Callable(*this, &SmartLoader::OnLayerGeometryReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable(*this, &SmartLoader::OnFrameReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable(*this, &SmartLoader::OnLayerReady)); + Register(*layerSource, &SmartLoader::OnLayerGeometryReady); + Register(*layerSource, &SmartLoader::OnFrameReady); + Register(*layerSource, &SmartLoader::OnLayerReady); dynamic_cast(layerSource.get())->LoadFrame(instanceId, frame); // keep a ref to the VolumeSlicer until the slice is fully loaded and saved to cache @@ -235,7 +227,7 @@ LOG(WARNING) << "Geometry ready: " << sliceKeyId; - boost::shared_ptr cachedSlice(new CachedSlice(IObserver::GetBroker())); + boost::shared_ptr cachedSlice(new CachedSlice); cachedSlice->slice_.reset(slice.Clone()); cachedSlice->effectiveQuality_ = source.GetImageQuality(); cachedSlice->status_ = CachedSliceStatus_GeometryLoaded; @@ -256,7 +248,7 @@ LOG(WARNING) << "Image ready: " << sliceKeyId; - boost::shared_ptr cachedSlice(new CachedSlice(IObserver::GetBroker())); + boost::shared_ptr cachedSlice(new CachedSlice); cachedSlice->image_.reset(Orthanc::Image::Clone(message.GetFrame())); cachedSlice->effectiveQuality_ = message.GetImageQuality(); cachedSlice->slice_.reset(message.GetSlice().Clone()); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/SmartLoader.h --- a/Framework/Deprecated/SmartLoader.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/SmartLoader.h Wed Apr 22 14:05:47 2020 +0200 @@ -30,7 +30,7 @@ { class SliceViewerWidget; - class SmartLoader : public OrthancStone::IObservable, public OrthancStone::IObserver + class SmartLoader : public OrthancStone::IObservable, public OrthancStone::ObserverBase { class CachedSlice; @@ -42,10 +42,10 @@ PreloadingInstances preloadingInstances_; SliceImageQuality imageQuality_; - OrthancApiClient& orthancApiClient_; + boost::shared_ptr orthancApiClient_; public: - SmartLoader(OrthancStone::MessageBroker& broker, OrthancApiClient& orthancApiClient); // TODO: add maxPreloadStorageSizeInBytes + SmartLoader(boost::shared_ptr orthancApiClient); // TODO: add maxPreloadStorageSizeInBytes // void PreloadStudy(const std::string studyId); // void PreloadSeries(const std::string seriesId); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Toolbox/BaseWebService.cpp --- a/Framework/Deprecated/Toolbox/BaseWebService.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Toolbox/BaseWebService.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -37,13 +37,13 @@ class BaseWebService::BaseWebServicePayload : public Orthanc::IDynamicObject { private: - std::unique_ptr< OrthancStone::MessageHandler > userSuccessHandler_; - std::unique_ptr< OrthancStone::MessageHandler > userFailureHandler_; + std::unique_ptr< MessageHandler > userSuccessHandler_; + std::unique_ptr< MessageHandler > userFailureHandler_; std::unique_ptr< Orthanc::IDynamicObject> userPayload_; public: - BaseWebServicePayload(OrthancStone::MessageHandler* userSuccessHandler, - OrthancStone::MessageHandler* userFailureHandler, + BaseWebServicePayload(MessageHandler* userSuccessHandler, + MessageHandler* userFailureHandler, Orthanc::IDynamicObject* userPayload) : userSuccessHandler_(userSuccessHandler), userFailureHandler_(userFailureHandler), @@ -88,18 +88,18 @@ void BaseWebService::GetAsync(const std::string& uri, const HttpHeaders& headers, Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback, + MessageHandler* successCallback, + MessageHandler* failureCallback, unsigned int timeoutInSeconds) { if (!cacheEnabled_ || cache_.find(uri) == cache_.end()) { GetAsyncInternal(uri, headers, new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered - new OrthancStone::Callable - (*this, &BaseWebService::CacheAndNotifyHttpSuccess), - new OrthancStone::Callable - (*this, &BaseWebService::NotifyHttpError), + new DeprecatedCallable + (GetSharedObserver(), &BaseWebService::CacheAndNotifyHttpSuccess), + new DeprecatedCallable + (GetSharedObserver(), &BaseWebService::NotifyHttpError), timeoutInSeconds); } else diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Toolbox/BaseWebService.h --- a/Framework/Deprecated/Toolbox/BaseWebService.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Toolbox/BaseWebService.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,6 +22,7 @@ #pragma once #include "IWebService.h" +#include "../../Messages/ObserverBase.h" #include #include @@ -31,7 +32,7 @@ { // This is an intermediate of IWebService that implements some caching on // the HTTP GET requests - class BaseWebService : public IWebService, public OrthancStone::IObserver + class BaseWebService : public IWebService, public OrthancStone::ObserverBase { public: class CachedHttpRequestSuccessMessage @@ -90,10 +91,7 @@ std::deque orderedCacheKeys_; public: - - BaseWebService(OrthancStone::MessageBroker& broker) : - IWebService(broker), - IObserver(broker), + BaseWebService() : cacheEnabled_(false), cacheCurrentSize_(0), cacheMaxSize_(100*1024*1024) @@ -112,21 +110,21 @@ virtual void GetAsync(const std::string& uri, const HttpHeaders& headers, Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, unsigned int timeoutInSeconds = 60); protected: virtual void GetAsyncInternal(const std::string& uri, const HttpHeaders& headers, Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, unsigned int timeoutInSeconds = 60) = 0; virtual void NotifyHttpSuccessLater(boost::shared_ptr cachedHttpMessage, Orthanc::IDynamicObject* payload, // takes ownership - OrthancStone::MessageHandler* successCallback) = 0; + MessageHandler* successCallback) = 0; private: void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Toolbox/IDelayedCallExecutor.h --- a/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h Wed Apr 22 14:05:47 2020 +0200 @@ -21,6 +21,7 @@ #pragma once +#include "IWebService.h" #include "../../Messages/IObserver.h" #include "../../Messages/ICallable.h" @@ -35,24 +36,14 @@ // 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* callback, + virtual void Schedule(MessageHandler* callback, unsigned int timeoutInMs = 1000) = 0; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Toolbox/IWebService.h --- a/Framework/Deprecated/Toolbox/IWebService.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Toolbox/IWebService.h Wed Apr 22 14:05:47 2020 +0200 @@ -33,6 +33,53 @@ namespace Deprecated { + template + class MessageHandler : public OrthancStone::ICallable + { + }; + + + template + class DeprecatedCallable : public MessageHandler + { + private: + typedef void (TObserver::* MemberMethod) (const TMessage&); + + boost::weak_ptr observer_; + MemberMethod function_; + + public: + DeprecatedCallable(boost::shared_ptr observer, + MemberMethod function) : + observer_(observer), + function_(function) + { + } + + virtual void Apply(const OrthancStone::IMessage& message) + { + boost::shared_ptr lock(observer_); + if (lock) + { + TObserver& observer = dynamic_cast(*lock); + const TMessage& typedMessage = dynamic_cast(message); + (observer.*function_) (typedMessage); + } + } + + virtual const OrthancStone::MessageIdentifier& GetMessageIdentifier() + { + return TMessage::GetStaticIdentifier(); + } + + virtual boost::weak_ptr GetObserver() const + { + return observer_; + } + }; + + // 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 @@ -40,9 +87,6 @@ // and you'll be notified when the response/error is ready. class IWebService : public boost::noncopyable { - protected: - OrthancStone::MessageBroker& broker_; - public: typedef std::map HttpHeaders; @@ -138,12 +182,6 @@ }; - IWebService(OrthancStone::MessageBroker& broker) : - broker_(broker) - { - } - - virtual ~IWebService() { } @@ -153,23 +191,23 @@ virtual void GetAsync(const std::string& uri, const HttpHeaders& headers, Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* 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* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, unsigned int timeoutInSeconds = 60) = 0; virtual void DeleteAsync(const std::string& uri, const HttpHeaders& headers, Orthanc::IDynamicObject* payload /* takes ownership */, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, unsigned int timeoutInSeconds = 60) = 0; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Toolbox/MessagingToolbox.cpp --- a/Framework/Deprecated/Toolbox/MessagingToolbox.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Toolbox/MessagingToolbox.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -18,7 +18,6 @@ * along with this program. If not, see . **/ - #include "MessagingToolbox.h" #include @@ -30,9 +29,16 @@ #include #include + +#ifdef _MSC_VER +// 'Json::Reader': Use CharReader and CharReaderBuilder instead +#pragma warning(disable:4996) +#endif + #include #include + namespace Deprecated { namespace MessagingToolbox diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Toolbox/OrthancApiClient.cpp --- a/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -68,12 +68,12 @@ class OrthancApiClient::WebServicePayload : public Orthanc::IDynamicObject { private: - std::unique_ptr< OrthancStone::MessageHandler > emptyHandler_; - std::unique_ptr< OrthancStone::MessageHandler > jsonHandler_; - std::unique_ptr< OrthancStone::MessageHandler > binaryHandler_; - std::unique_ptr< OrthancStone::MessageHandler > failureHandler_; + std::unique_ptr< MessageHandler > emptyHandler_; + std::unique_ptr< MessageHandler > jsonHandler_; + std::unique_ptr< MessageHandler > binaryHandler_; + std::unique_ptr< MessageHandler > failureHandler_; std::unique_ptr< Orthanc::IDynamicObject > userPayload_; - OrthancStone::MessageBroker& broker_; + void NotifyConversionError(const IWebService::HttpRequestSuccessMessage& message) const { if (failureHandler_.get() != NULL) @@ -84,14 +84,12 @@ } public: - WebServicePayload(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* handler, - OrthancStone::MessageHandler* failureHandler, + WebServicePayload(MessageHandler* handler, + MessageHandler* failureHandler, Orthanc::IDynamicObject* userPayload) : emptyHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload), - broker_(broker) + userPayload_(userPayload) { if (handler == NULL) @@ -100,14 +98,12 @@ } } - WebServicePayload(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* handler, - OrthancStone::MessageHandler* failureHandler, + WebServicePayload(MessageHandler* handler, + MessageHandler* failureHandler, Orthanc::IDynamicObject* userPayload) : binaryHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload), - broker_(broker) + userPayload_(userPayload) { if (handler == NULL) { @@ -115,14 +111,12 @@ } } - WebServicePayload(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* handler, - OrthancStone::MessageHandler* failureHandler, + WebServicePayload(MessageHandler* handler, + MessageHandler* failureHandler, Orthanc::IDynamicObject* userPayload) : jsonHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload), - broker_(broker) + userPayload_(userPayload) { if (handler == NULL) { @@ -134,35 +128,26 @@ { if (emptyHandler_.get() != NULL) { - if (broker_.IsActive(*(emptyHandler_->GetObserver()))) - { - emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage - (message.GetUri(), userPayload_.get())); - } + emptyHandler_->Apply(OrthancApiClient::EmptyResponseReadyMessage + (message.GetUri(), userPayload_.get())); } else if (binaryHandler_.get() != NULL) { - if (broker_.IsActive(*(binaryHandler_->GetObserver()))) - { - binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage - (message.GetUri(), message.GetAnswer(), - message.GetAnswerSize(), userPayload_.get())); - } + binaryHandler_->Apply(OrthancApiClient::BinaryResponseReadyMessage + (message.GetUri(), message.GetAnswer(), + message.GetAnswerSize(), userPayload_.get())); } else if (jsonHandler_.get() != NULL) { - if (broker_.IsActive(*(jsonHandler_->GetObserver()))) + Json::Value response; + if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) { - Json::Value response; - if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) - { - jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage - (message.GetUri(), response, userPayload_.get())); - } - else - { - NotifyConversionError(message); - } + jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage + (message.GetUri(), response, userPayload_.get())); + } + else + { + NotifyConversionError(message); } } else @@ -182,11 +167,8 @@ }; - OrthancApiClient::OrthancApiClient(OrthancStone::MessageBroker& broker, - IWebService& web, + OrthancApiClient::OrthancApiClient(IWebService& web, const std::string& baseUrl) : - IObservable(broker), - IObserver(broker), web_(web), baseUrl_(baseUrl) { @@ -195,26 +177,26 @@ void OrthancApiClient::GetJsonAsync( const std::string& uri, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback, + MessageHandler* successCallback, + MessageHandler* failureCallback, Orthanc::IDynamicObject* payload) { IWebService::HttpHeaders emptyHeaders; web_.GetAsync(baseUrl_ + uri, emptyHeaders, - new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), - new OrthancStone::Callable - (*this, &OrthancApiClient::NotifyHttpSuccess), - new OrthancStone::Callable - (*this, &OrthancApiClient::NotifyHttpError)); + new WebServicePayload(successCallback, failureCallback, payload), + new DeprecatedCallable + (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess), + new DeprecatedCallable + (GetSharedObserver(), &OrthancApiClient::NotifyHttpError)); } void OrthancApiClient::GetBinaryAsync( const std::string& uri, const std::string& contentType, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback, + MessageHandler* successCallback, + MessageHandler* failureCallback, Orthanc::IDynamicObject* payload) { IWebService::HttpHeaders headers; @@ -225,34 +207,34 @@ void OrthancApiClient::GetBinaryAsync( const std::string& uri, const IWebService::HttpHeaders& headers, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback, + MessageHandler* successCallback, + MessageHandler* failureCallback, Orthanc::IDynamicObject* payload) { // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str()); web_.GetAsync(baseUrl_ + uri, headers, - new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), - new OrthancStone::Callable - (*this, &OrthancApiClient::NotifyHttpSuccess), - new OrthancStone::Callable - (*this, &OrthancApiClient::NotifyHttpError)); + new WebServicePayload(successCallback, failureCallback, payload), + new DeprecatedCallable + (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess), + new DeprecatedCallable + (GetSharedObserver(), &OrthancApiClient::NotifyHttpError)); } void OrthancApiClient::PostBinaryAsyncExpectJson( const std::string& uri, const std::string& body, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback, + MessageHandler* successCallback, + MessageHandler* failureCallback, Orthanc::IDynamicObject* payload) { web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), - new OrthancStone::Callable - (*this, &OrthancApiClient::NotifyHttpSuccess), - new OrthancStone::Callable - (*this, &OrthancApiClient::NotifyHttpError)); + new WebServicePayload(successCallback, failureCallback, payload), + new DeprecatedCallable + (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess), + new DeprecatedCallable + (GetSharedObserver(), &OrthancApiClient::NotifyHttpError)); } @@ -266,23 +248,23 @@ void OrthancApiClient::PostBinaryAsync( const std::string& uri, const std::string& body, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback, + MessageHandler* successCallback, + MessageHandler* failureCallback, Orthanc::IDynamicObject* payload /* takes ownership */) { web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), - new OrthancStone::Callable - (*this, &OrthancApiClient::NotifyHttpSuccess), - new OrthancStone::Callable - (*this, &OrthancApiClient::NotifyHttpError)); + new WebServicePayload(successCallback, failureCallback, payload), + new DeprecatedCallable + (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess), + new DeprecatedCallable + (GetSharedObserver(), &OrthancApiClient::NotifyHttpError)); } void OrthancApiClient::PostJsonAsyncExpectJson( const std::string& uri, const Json::Value& data, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback, + MessageHandler* successCallback, + MessageHandler* failureCallback, Orthanc::IDynamicObject* payload) { std::string body; @@ -302,8 +284,8 @@ void OrthancApiClient::PostJsonAsync( const std::string& uri, const Json::Value& data, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback, + MessageHandler* successCallback, + MessageHandler* failureCallback, Orthanc::IDynamicObject* payload /* takes ownership */) { std::string body; @@ -313,16 +295,16 @@ void OrthancApiClient::DeleteAsync( const std::string& uri, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback, + MessageHandler* successCallback, + MessageHandler* failureCallback, Orthanc::IDynamicObject* payload) { web_.DeleteAsync(baseUrl_ + uri, IWebService::HttpHeaders(), - new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), - new OrthancStone::Callable - (*this, &OrthancApiClient::NotifyHttpSuccess), - new OrthancStone::Callable - (*this, &OrthancApiClient::NotifyHttpError)); + new WebServicePayload(successCallback, failureCallback, payload), + new DeprecatedCallable + (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess), + new DeprecatedCallable + (GetSharedObserver(), &OrthancApiClient::NotifyHttpError)); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Toolbox/OrthancApiClient.h --- a/Framework/Deprecated/Toolbox/OrthancApiClient.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Toolbox/OrthancApiClient.h Wed Apr 22 14:05:47 2020 +0200 @@ -25,13 +25,24 @@ #include #include "IWebService.h" -#include "../../Messages/IObservable.h" +#include "../../Messages/ObserverBase.h" 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) + }; + class OrthancApiClient : public OrthancStone::IObservable, - public OrthancStone::IObserver + public OrthancStone::ObserverBase { public: class JsonResponseReadyMessage : public OrthancStone::IMessage @@ -157,8 +168,7 @@ std::string baseUrl_; public: - OrthancApiClient(OrthancStone::MessageBroker& broker, - IWebService& web, + OrthancApiClient(IWebService& web, const std::string& baseUrl); virtual ~OrthancApiClient() @@ -169,36 +179,36 @@ // schedule a GET request expecting a JSON response. void GetJsonAsync(const std::string& uri, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* 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* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* 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* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* 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* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* 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* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, Orthanc::IDynamicObject* payload = NULL /* takes ownership */); // schedule a POST request and don't mind the response. @@ -208,8 +218,8 @@ // schedule a POST request and don't expect any response. void PostJsonAsync(const std::string& uri, const Json::Value& data, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, Orthanc::IDynamicObject* payload = NULL /* takes ownership */); @@ -220,14 +230,14 @@ // schedule a POST request and don't expect any response. void PostBinaryAsync(const std::string& uri, const std::string& body, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, Orthanc::IDynamicObject* payload = NULL /* takes ownership */); // schedule a DELETE request expecting an empty response. void DeleteAsync(const std::string& uri, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, Orthanc::IDynamicObject* payload = NULL /* takes ownership */); void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp --- a/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -639,10 +639,7 @@ } - OrthancSlicesLoader::OrthancSlicesLoader(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc) : - OrthancStone::IObservable(broker), - OrthancStone::IObserver(broker), + OrthancSlicesLoader::OrthancSlicesLoader(boost::shared_ptr orthanc) : orthanc_(orthanc), state_(State_Initialization) { @@ -658,10 +655,10 @@ else { state_ = State_LoadingGeometry; - orthanc_.GetJsonAsync("/series/" + seriesId + "/instances-tags", - new OrthancStone::Callable(*this, &OrthancSlicesLoader::ParseSeriesGeometry), - new OrthancStone::Callable(*this, &OrthancSlicesLoader::OnGeometryError), - NULL); + orthanc_->GetJsonAsync("/series/" + seriesId + "/instances-tags", + new DeprecatedCallable(GetSharedObserver(), &OrthancSlicesLoader::ParseSeriesGeometry), + new DeprecatedCallable(GetSharedObserver(), &OrthancSlicesLoader::OnGeometryError), + NULL); } } @@ -677,10 +674,10 @@ // 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(*this, &OrthancSlicesLoader::ParseInstanceGeometry), - new OrthancStone::Callable(*this, &OrthancSlicesLoader::OnGeometryError), - Operation::DownloadInstanceGeometry(instanceId)); + orthanc_->GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c", + new DeprecatedCallable(GetSharedObserver(), &OrthancSlicesLoader::ParseInstanceGeometry), + new DeprecatedCallable(GetSharedObserver(), &OrthancSlicesLoader::OnGeometryError), + Operation::DownloadInstanceGeometry(instanceId)); } } @@ -696,10 +693,10 @@ { state_ = State_LoadingGeometry; - orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags", - new OrthancStone::Callable(*this, &OrthancSlicesLoader::ParseFrameGeometry), - new OrthancStone::Callable(*this, &OrthancSlicesLoader::OnGeometryError), - Operation::DownloadFrameGeometry(instanceId, frame)); + orthanc_->GetJsonAsync("/instances/" + instanceId + "/tags", + new DeprecatedCallable(GetSharedObserver(), &OrthancSlicesLoader::ParseFrameGeometry), + new DeprecatedCallable(GetSharedObserver(), &OrthancSlicesLoader::OnGeometryError), + Operation::DownloadFrameGeometry(instanceId, frame)); } } @@ -770,23 +767,23 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - orthanc_.GetBinaryAsync(uri, "image/png", - new OrthancStone::Callable - (*this, &OrthancSlicesLoader::ParseSliceImagePng), - new OrthancStone::Callable - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceImage( - static_cast(index), slice, SliceImageQuality_FullPng)); -} + orthanc_->GetBinaryAsync(uri, "image/png", + new DeprecatedCallable + (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceImagePng), + new DeprecatedCallable + (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage( + static_cast(index), slice, SliceImageQuality_FullPng)); + } void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice, size_t index) { std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + - boost::lexical_cast(slice.GetFrame())); + boost::lexical_cast(slice.GetFrame())); switch (slice.GetConverter().GetExpectedPixelFormat()) { @@ -806,15 +803,15 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - orthanc_.GetBinaryAsync(uri, "image/x-portable-arbitrarymap", - new OrthancStone::Callable - (*this, &OrthancSlicesLoader::ParseSliceImagePam), - new OrthancStone::Callable - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceImage(static_cast(index), - slice, SliceImageQuality_FullPam)); + orthanc_->GetBinaryAsync(uri, "image/x-portable-arbitrarymap", + new DeprecatedCallable + (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceImagePam), + new DeprecatedCallable + (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage(static_cast(index), + slice, SliceImageQuality_FullPam)); } @@ -849,15 +846,15 @@ "-" + slice.GetOrthancInstanceId() + "_" + boost::lexical_cast(slice.GetFrame())); - orthanc_.GetJsonAsync(uri, - new OrthancStone::Callable - (*this, &OrthancSlicesLoader::ParseSliceImageJpeg), - new OrthancStone::Callable - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceImage( - static_cast(index), slice, quality)); + orthanc_->GetJsonAsync(uri, + new DeprecatedCallable + (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceImageJpeg), + new DeprecatedCallable + (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage( + static_cast(index), slice, quality)); } @@ -890,15 +887,15 @@ { std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + boost::lexical_cast(slice.GetFrame()) + "/raw.gz"); - orthanc_.GetBinaryAsync(uri, IWebService::HttpHeaders(), - new OrthancStone::Callable - (*this, &OrthancSlicesLoader::ParseSliceRawImage), - new OrthancStone::Callable - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceRawImage( - static_cast(index), slice)); + orthanc_->GetBinaryAsync(uri, IWebService::HttpHeaders(), + new DeprecatedCallable + (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceRawImage), + new DeprecatedCallable + (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceRawImage( + static_cast(index), slice)); } } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Toolbox/OrthancSlicesLoader.h --- a/Framework/Deprecated/Toolbox/OrthancSlicesLoader.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,6 +22,7 @@ #pragma once #include "../../Messages/IObservable.h" +#include "../../Messages/ObserverBase.h" #include "../../StoneEnumerations.h" #include "../../Toolbox/SlicesSorter.h" #include "IWebService.h" @@ -33,7 +34,9 @@ namespace Deprecated { - class OrthancSlicesLoader : public OrthancStone::IObservable, public OrthancStone::IObserver + class OrthancSlicesLoader : + public OrthancStone::IObservable, + public OrthancStone::ObserverBase { public: ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryReadyMessage, OrthancSlicesLoader); @@ -143,7 +146,7 @@ class Operation; - OrthancApiClient& orthanc_; + boost::shared_ptr orthanc_; State state_; OrthancStone::SlicesSorter slices_; @@ -183,9 +186,8 @@ void SortAndFinalizeSlices(); public: - OrthancSlicesLoader(OrthancStone::MessageBroker& broker, - //ISliceLoaderObserver& callback, - OrthancApiClient& orthancApi); + OrthancSlicesLoader(//ISliceLoaderObserver& callback, + boost::shared_ptr orthancApi); void ScheduleLoadSeries(const std::string& seriesId); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Viewport/IViewport.h --- a/Framework/Deprecated/Viewport/IViewport.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Viewport/IViewport.h Wed Apr 22 14:05:47 2020 +0200 @@ -37,15 +37,6 @@ 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; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Viewport/WidgetViewport.cpp --- a/Framework/Deprecated/Viewport/WidgetViewport.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Viewport/WidgetViewport.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -26,8 +26,7 @@ namespace Deprecated { - WidgetViewport::WidgetViewport(OrthancStone::MessageBroker& broker) : - IViewport(broker), + WidgetViewport::WidgetViewport() : statusBar_(NULL), isMouseOver_(false), lastMouseX_(0), @@ -57,7 +56,7 @@ } - IWidget& WidgetViewport::SetCentralWidget(IWidget* widget) + void WidgetViewport::SetCentralWidget(boost::shared_ptr widget) { if (widget == NULL) { @@ -66,7 +65,7 @@ mouseTracker_.reset(NULL); - centralWidget_.reset(widget); + centralWidget_ = widget; centralWidget_->SetViewport(*this); if (statusBar_ != NULL) @@ -75,8 +74,6 @@ } NotifyBackgroundChanged(); - - return *widget; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Viewport/WidgetViewport.h --- a/Framework/Deprecated/Viewport/WidgetViewport.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Viewport/WidgetViewport.h Wed Apr 22 14:05:47 2020 +0200 @@ -33,7 +33,7 @@ class WidgetViewport : public IViewport { private: - std::unique_ptr centralWidget_; + boost::shared_ptr centralWidget_; IStatusBar* statusBar_; std::unique_ptr mouseTracker_; bool isMouseOver_; @@ -43,13 +43,13 @@ bool backgroundChanged_; public: - WidgetViewport(OrthancStone::MessageBroker& broker); + WidgetViewport(); virtual void FitContent(); virtual void SetStatusBar(IStatusBar& statusBar); - IWidget& SetCentralWidget(IWidget* widget); // Takes ownership + void SetCentralWidget(boost::shared_ptr widget); virtual void NotifyBackgroundChanged(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Volumes/ISlicedVolume.h --- a/Framework/Deprecated/Volumes/ISlicedVolume.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Volumes/ISlicedVolume.h Wed Apr 22 14:05:47 2020 +0200 @@ -65,11 +65,6 @@ }; - ISlicedVolume(OrthancStone::MessageBroker& broker) : - IObservable(broker) - { - } - virtual size_t GetSliceCount() const = 0; virtual const Slice& GetSlice(size_t slice) const = 0; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Volumes/IVolumeLoader.h --- a/Framework/Deprecated/Volumes/IVolumeLoader.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Volumes/IVolumeLoader.h Wed Apr 22 14:05:47 2020 +0200 @@ -31,10 +31,5 @@ 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) - { - } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Volumes/StructureSetLoader.cpp --- a/Framework/Deprecated/Volumes/StructureSetLoader.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Volumes/StructureSetLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -27,10 +27,7 @@ namespace Deprecated { - StructureSetLoader::StructureSetLoader(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc) : - IVolumeLoader(broker), - IObserver(broker), + StructureSetLoader::StructureSetLoader(OrthancApiClient& orthanc) : orthanc_(orthanc) { } @@ -60,7 +57,7 @@ it != instances.end(); ++it) { orthanc_.PostBinaryAsyncExpectJson("/tools/lookup", *it, - new OrthancStone::Callable(*this, &StructureSetLoader::OnLookupCompleted)); + new DeprecatedCallable(GetSharedObserver(), &StructureSetLoader::OnLookupCompleted)); } BroadcastMessage(GeometryReadyMessage(*this)); @@ -84,7 +81,7 @@ const std::string& instance = lookup[0]["ID"].asString(); orthanc_.GetJsonAsync("/instances/" + instance + "/tags", - new OrthancStone::Callable(*this, &StructureSetLoader::OnReferencedSliceLoaded)); + new DeprecatedCallable(GetSharedObserver(), &StructureSetLoader::OnReferencedSliceLoaded)); } @@ -97,7 +94,7 @@ else { orthanc_.GetJsonAsync("/instances/" + instance + "/tags?ignore-length=3006-0050", - new OrthancStone::Callable(*this, &StructureSetLoader::OnStructureSetLoaded)); + new DeprecatedCallable(GetSharedObserver(), &StructureSetLoader::OnStructureSetLoaded)); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Volumes/StructureSetLoader.h --- a/Framework/Deprecated/Volumes/StructureSetLoader.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Volumes/StructureSetLoader.h Wed Apr 22 14:05:47 2020 +0200 @@ -21,6 +21,7 @@ #pragma once +#include "../../Messages/ObserverBase.h" #include "../../Toolbox/DicomStructureSet.h" #include "../Toolbox/OrthancApiClient.h" #include "IVolumeLoader.h" @@ -31,7 +32,7 @@ { class StructureSetLoader : public IVolumeLoader, - public OrthancStone::IObserver + public OrthancStone::ObserverBase { private: OrthancApiClient& orthanc_; @@ -44,8 +45,7 @@ void OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message); public: - StructureSetLoader(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc); + StructureSetLoader(OrthancApiClient& orthanc); void ScheduleLoadInstance(const std::string& instance); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Widgets/LayoutWidget.cpp --- a/Framework/Deprecated/Widgets/LayoutWidget.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Widgets/LayoutWidget.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -85,14 +85,14 @@ class LayoutWidget::ChildWidget : public boost::noncopyable { private: - std::unique_ptr widget_; + boost::shared_ptr widget_; int left_; int top_; unsigned int width_; unsigned int height_; public: - ChildWidget(IWidget* widget) : + ChildWidget(boost::shared_ptr widget) : widget_(widget) { assert(widget != NULL); @@ -354,7 +354,7 @@ } - IWidget& LayoutWidget::AddWidget(IWidget* widget) // Takes ownership + void LayoutWidget::AddWidget(boost::shared_ptr widget) // Takes ownership { if (widget == NULL) { @@ -375,8 +375,6 @@ { hasAnimation_ = true; } - - return *widget; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Widgets/LayoutWidget.h --- a/Framework/Deprecated/Widgets/LayoutWidget.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Widgets/LayoutWidget.h Wed Apr 22 14:05:47 2020 +0200 @@ -94,7 +94,7 @@ return paddingInternal_; } - IWidget& AddWidget(IWidget* widget); // Takes ownership + void AddWidget(boost::shared_ptr widget); virtual void SetStatusBar(IStatusBar& statusBar); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Widgets/SliceViewerWidget.cpp --- a/Framework/Deprecated/Widgets/SliceViewerWidget.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Widgets/SliceViewerWidget.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -35,206 +35,174 @@ namespace Deprecated { - class SliceViewerWidget::Scene : public boost::noncopyable + void SliceViewerWidget::Scene::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_++; + } + } + + + SliceViewerWidget::Scene::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); + } + } + + + SliceViewerWidget::Scene::~Scene() + { + for (size_t i = 0; i < renderers_.size(); i++) + { + DeleteLayer(i); + } + } + + void SliceViewerWidget::Scene::SetLayer(size_t index, + ILayerRenderer* renderer) // Takes ownership { - private: - OrthancStone::CoordinateSystem3D plane_; - double thickness_; - size_t countMissing_; - std::vector renderers_; + if (renderer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + DeleteLayer(index); + + renderers_[index] = renderer; + countMissing_--; + } + + + bool SliceViewerWidget::Scene::RenderScene(OrthancStone::CairoContext& context, + const ViewportGeometry& view, + const OrthancStone::CoordinateSystem3D& viewportPlane) + { + bool fullQuality = true; + cairo_t *cr = context.GetObject(); - public: - void DeleteLayer(size_t index) + for (size_t i = 0; i < renderers_.size(); i++) { - if (index >= renderers_.size()) + if (renderers_[i] != NULL) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + 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); } - assert(countMissing_ <= renderers_.size()); - - if (renderers_[index] != NULL) + if (renderers_[i] != NULL && + !renderers_[i]->IsFullQuality()) { - 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); + fullQuality = false; } } - 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 + if (!fullQuality) { - return plane_; - } + double x, y; + view.MapDisplayToScene(x, y, static_cast(view.GetDisplayWidth()) / 2.0, 10); - bool HasRenderer(size_t index) - { - return renderers_[index] != NULL; - } + cairo_translate(cr, x, y); - bool IsComplete() const - { - return countMissing_ == 0; - } - - unsigned int GetCountMissing() const - { - return static_cast(countMissing_); +#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()); +#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); } - bool RenderScene(OrthancStone::CairoContext& context, - const ViewportGeometry& view, - const OrthancStone::CoordinateSystem3D& viewportPlane) - { - bool fullQuality = true; - cairo_t *cr = context.GetObject(); + return true; + } - 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()); + void SliceViewerWidget::Scene::SetLayerStyle(size_t index, + const RenderStyle& style) + { + if (renderers_[index] != NULL) + { + renderers_[index]->SetLayerStyle(style); + } + } - /** - * 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); + bool SliceViewerWidget::Scene::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())); - 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 (z < 0) + { + z = -z; } - if (!fullQuality) - { - double x, y; - view.MapDisplayToScene(x, y, static_cast(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()); -#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; + return z <= thickness_; } - - 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 */, @@ -250,7 +218,7 @@ { index = found->second; assert(index < layers_.size() && - layers_[index] == &layer); + layers_[index].get() == &layer); return true; } } @@ -369,42 +337,27 @@ } - SliceViewerWidget::SliceViewerWidget(OrthancStone::MessageBroker& broker, - const std::string& name) : + SliceViewerWidget::SliceViewerWidget(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 - (*this, &SliceViewerWidget::OnGeometryReady)); - // currently ignore errors layer->RegisterObserverCallback(new Callable(*this, &SliceViewerWidget::...)); - layer.RegisterObserverCallback(new OrthancStone::Callable - (*this, &SliceViewerWidget::OnSliceChanged)); - layer.RegisterObserverCallback(new OrthancStone::Callable - (*this, &SliceViewerWidget::OnContentChanged)); - layer.RegisterObserverCallback(new OrthancStone::Callable - (*this, &SliceViewerWidget::OnLayerReady)); - layer.RegisterObserverCallback(new OrthancStone::Callable - (*this, &SliceViewerWidget::OnLayerError)); + // currently ignoring errors of type IVolumeSlicer::GeometryErrorMessage + + Register(layer, &SliceViewerWidget::OnGeometryReady); + Register(layer, &SliceViewerWidget::OnSliceChanged); + Register(layer, &SliceViewerWidget::OnContentChanged); + Register(layer, &SliceViewerWidget::OnLayerReady); + Register(layer, &SliceViewerWidget::OnLayerError); } - size_t SliceViewerWidget::AddLayer(IVolumeSlicer* layer) // Takes ownership + size_t SliceViewerWidget::AddLayer(boost::shared_ptr layer) { if (layer == NULL) { @@ -414,7 +367,7 @@ size_t index = layers_.size(); layers_.push_back(layer); styles_.push_back(RenderStyle()); - layersIndex_[layer] = index; + layersIndex_[layer.get()] = index; ResetPendingScene(); @@ -426,7 +379,8 @@ } - void SliceViewerWidget::ReplaceLayer(size_t index, IVolumeSlicer* layer) // Takes ownership + void SliceViewerWidget::ReplaceLayer(size_t index, + boost::shared_ptr layer) { if (layer == NULL) { @@ -438,9 +392,8 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - delete layers_[index]; layers_[index] = layer; - layersIndex_[layer] = index; + layersIndex_[layer.get()] = index; ResetPendingScene(); @@ -457,13 +410,13 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - IVolumeSlicer* previousLayer = layers_[index]; + IVolumeSlicer* previousLayer = layers_[index].get(); layersIndex_.erase(layersIndex_.find(previousLayer)); layers_.erase(layers_.begin() + index); changedLayers_.erase(changedLayers_.begin() + index); styles_.erase(styles_.begin() + index); - delete layers_[index]; + layers_[index].reset(); currentScene_->DeleteLayer(index); ResetPendingScene(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Deprecated/Widgets/SliceViewerWidget.h --- a/Framework/Deprecated/Widgets/SliceViewerWidget.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Deprecated/Widgets/SliceViewerWidget.h Wed Apr 22 14:05:47 2020 +0200 @@ -24,7 +24,7 @@ #include "WorldSceneWidget.h" #include "../Layers/IVolumeSlicer.h" #include "../../Toolbox/Extent2D.h" -#include "../../Messages/IObserver.h" +#include "../../Messages/ObserverBase.h" #include @@ -32,7 +32,7 @@ { class SliceViewerWidget : public WorldSceneWidget, - public OrthancStone::IObserver, + public OrthancStone::ObserverBase, public OrthancStone::IObservable { public: @@ -66,13 +66,67 @@ SliceViewerWidget(const SliceViewerWidget&); SliceViewerWidget& operator=(const SliceViewerWidget&); - class Scene; + class Scene : public boost::noncopyable + { + private: + OrthancStone::CoordinateSystem3D plane_; + double thickness_; + size_t countMissing_; + std::vector renderers_; + + public: + void DeleteLayer(size_t index); + + Scene(const OrthancStone::CoordinateSystem3D& plane, + double thickness, + size_t countLayers); + + ~Scene(); + + void SetLayer(size_t index, + ILayerRenderer* renderer); // Takes ownership + + 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(countMissing_); + } + + bool RenderScene(OrthancStone::CairoContext& context, + const ViewportGeometry& view, + const OrthancStone::CoordinateSystem3D& viewportPlane); + + void SetLayerStyle(size_t index, + const RenderStyle& style); + + bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const; + + double GetThickness() const + { + return thickness_; + } + }; + typedef std::map LayersIndex; bool started_; LayersIndex layersIndex_; - std::vector layers_; + std::vector > layers_; std::vector styles_; OrthancStone::CoordinateSystem3D plane_; std::unique_ptr currentScene_; @@ -100,8 +154,7 @@ void ResetChangedLayers(); public: - SliceViewerWidget(OrthancStone::MessageBroker& broker, - const std::string& name); + SliceViewerWidget(const std::string& name); virtual OrthancStone::Extent2D GetSceneExtent(); @@ -120,11 +173,13 @@ void InvalidateLayer(size_t layer); public: - virtual ~SliceViewerWidget(); + virtual ~SliceViewerWidget() + { + } - size_t AddLayer(IVolumeSlicer* layer); // Takes ownership + size_t AddLayer(boost::shared_ptr layer); - void ReplaceLayer(size_t layerIndex, IVolumeSlicer* layer); // Takes ownership + void ReplaceLayer(size_t layerIndex, boost::shared_ptr layer); // Takes ownership void RemoveLayer(size_t layerIndex); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Fonts/GlyphTextureAlphabet.cpp --- a/Framework/Fonts/GlyphTextureAlphabet.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Fonts/GlyphTextureAlphabet.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -28,7 +28,7 @@ #include #include -#ifdef __EMSCRIPTEN__ +#if defined(__EMSCRIPTEN__) /* Avoid this error: .../boost/math/special_functions/round.hpp:86:12: warning: implicit conversion from 'std::__2::numeric_limits::type' (aka 'int') to 'float' changes value from 2147483647 to 2147483648 [-Wimplicit-int-float-conversion] diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/DicomResourcesLoader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/DicomResourcesLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,909 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "DicomResourcesLoader.h" + +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "../Oracle/ParseDicomFromFileCommand.h" +# include +#endif + +#include + +namespace OrthancStone +{ + static std::string GetUri(Orthanc::ResourceType level) + { + switch (level) + { + case Orthanc::ResourceType_Patient: + return "patients"; + + case Orthanc::ResourceType_Study: + return "studies"; + + case Orthanc::ResourceType_Series: + return "series"; + + case Orthanc::ResourceType_Instance: + return "instances"; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + class DicomResourcesLoader::Handler : public Orthanc::IDynamicObject + { + private: + boost::shared_ptr loader_; + boost::shared_ptr target_; + int priority_; + DicomSource source_; + boost::shared_ptr userPayload_; + + public: + Handler(boost::shared_ptr loader, + boost::shared_ptr target, + int priority, + const DicomSource& source, + boost::shared_ptr userPayload) : + loader_(loader), + target_(target), + priority_(priority), + source_(source), + userPayload_(userPayload) + { + if (!loader || + !target) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + virtual ~Handler() + { + } + + void BroadcastSuccess() + { + SuccessMessage message(*loader_, target_, priority_, source_, userPayload_.get()); + loader_->BroadcastMessage(message); + } + + boost::shared_ptr GetLoader() + { + assert(loader_); + return loader_; + } + + boost::shared_ptr GetTarget() + { + assert(target_); + return target_; + } + + int GetPriority() const + { + return priority_; + } + + const DicomSource& GetSource() const + { + return source_; + } + + const boost::shared_ptr GetUserPayload() const + { + return userPayload_; + } + }; + + + class DicomResourcesLoader::StringHandler : public DicomResourcesLoader::Handler + { + public: + StringHandler(boost::shared_ptr loader, + boost::shared_ptr target, + int priority, + const DicomSource& source, + boost::shared_ptr userPayload) : + Handler(loader, target, priority, source, userPayload) + { + } + + virtual void HandleJson(const Json::Value& body) = 0; + + virtual void HandleString(const std::string& body) + { + Json::Reader reader; + Json::Value value; + if (reader.parse(body, value)) + { + HandleJson(value); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + }; + + + class DicomResourcesLoader::DicomWebHandler : public StringHandler + { + public: + DicomWebHandler(boost::shared_ptr loader, + boost::shared_ptr target, + int priority, + const DicomSource& source, + boost::shared_ptr userPayload) : + StringHandler(loader, target, priority, source, userPayload) + { + } + + virtual void HandleJson(const Json::Value& body) + { + GetTarget()->AddFromDicomWeb(body); + BroadcastSuccess(); + } + }; + + + class DicomResourcesLoader::OrthancHandler : public StringHandler + { + private: + boost::shared_ptr remainingCommands_; + + protected: + void CloseCommand() + { + assert(remainingCommands_); + + if (*remainingCommands_ == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + (*remainingCommands_) --; + + if (*remainingCommands_ == 0) + { + BroadcastSuccess(); + } + } + + public: + OrthancHandler(boost::shared_ptr loader, + boost::shared_ptr target, + int priority, + const DicomSource& source, + boost::shared_ptr remainingCommands, + boost::shared_ptr userPayload) : + StringHandler(loader, target, priority, source, userPayload), + remainingCommands_(remainingCommands) + { + if (!remainingCommands) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + (*remainingCommands) ++; + } + + boost::shared_ptr GetRemainingCommands() + { + assert(remainingCommands_); + return remainingCommands_; + } + }; + + + class DicomResourcesLoader::OrthancInstanceTagsHandler : public OrthancHandler + { + public: + OrthancInstanceTagsHandler(boost::shared_ptr loader, + boost::shared_ptr target, + int priority, + const DicomSource& source, + boost::shared_ptr remainingCommands, + boost::shared_ptr userPayload) : + OrthancHandler(loader, target, priority, source, remainingCommands, userPayload) + { + } + + virtual void HandleJson(const Json::Value& body) + { + GetTarget()->AddFromOrthanc(body); + CloseCommand(); + } + }; + + + class DicomResourcesLoader::OrthancOneChildInstanceHandler : public OrthancHandler + { + public: + OrthancOneChildInstanceHandler(boost::shared_ptr loader, + boost::shared_ptr target, + int priority, + const DicomSource& source, + boost::shared_ptr remainingCommands, + boost::shared_ptr userPayload) : + OrthancHandler(loader, target, priority, source, remainingCommands, userPayload) + { + } + + virtual void HandleJson(const Json::Value& body) + { + static const char* const ID = "ID"; + + if (body.type() == Json::arrayValue) + { + if (body.size() > 0) + { + if (body[0].type() == Json::objectValue && + body[0].isMember(ID) && + body[0][ID].type() == Json::stringValue) + { + GetLoader()->ScheduleLoadOrthancInstanceTags + (GetTarget(), GetPriority(), GetSource(), body[0][ID].asString(), GetRemainingCommands(), GetUserPayload()); + CloseCommand(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + }; + + + class DicomResourcesLoader::OrthancAllChildrenInstancesHandler : public OrthancHandler + { + private: + Orthanc::ResourceType bottomLevel_; + + public: + OrthancAllChildrenInstancesHandler(boost::shared_ptr loader, + boost::shared_ptr target, + int priority, + const DicomSource& source, + boost::shared_ptr remainingCommands, + Orthanc::ResourceType bottomLevel, + boost::shared_ptr userPayload) : + OrthancHandler(loader, target, priority, source, remainingCommands, userPayload), + bottomLevel_(bottomLevel) + { + } + + virtual void HandleJson(const Json::Value& body) + { + static const char* const ID = "ID"; + static const char* const INSTANCES = "Instances"; + + if (body.type() == Json::arrayValue) + { + for (Json::Value::ArrayIndex i = 0; i < body.size(); i++) + { + switch (bottomLevel_) + { + case Orthanc::ResourceType_Patient: + case Orthanc::ResourceType_Study: + if (body[i].type() == Json::objectValue && + body[i].isMember(ID) && + body[i][ID].type() == Json::stringValue) + { + GetLoader()->ScheduleLoadOrthancOneChildInstance + (GetTarget(), GetPriority(), GetSource(), bottomLevel_, + body[i][ID].asString(), GetRemainingCommands(), GetUserPayload()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + break; + + case Orthanc::ResourceType_Series: + // At the series level, avoid a call to + // "/series/.../instances", as we already have this + // information in the JSON + if (body[i].type() == Json::objectValue && + body[i].isMember(INSTANCES) && + body[i][INSTANCES].type() == Json::arrayValue) + { + if (body[i][INSTANCES].size() > 0) + { + if (body[i][INSTANCES][0].type() == Json::stringValue) + { + GetLoader()->ScheduleLoadOrthancInstanceTags + (GetTarget(), GetPriority(), GetSource(), + body[i][INSTANCES][0].asString(), GetRemainingCommands(), GetUserPayload()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + } + + break; + + case Orthanc::ResourceType_Instance: + if (body[i].type() == Json::objectValue && + body[i].isMember(ID) && + body[i][ID].type() == Json::stringValue) + { + GetLoader()->ScheduleLoadOrthancInstanceTags + (GetTarget(), GetPriority(), GetSource(), + body[i][ID].asString(), GetRemainingCommands(), GetUserPayload()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + + CloseCommand(); + } + }; + + +#if ORTHANC_ENABLE_DCMTK == 1 + static void ExploreDicomDir(OrthancStone::LoadedDicomResources& instances, + const Orthanc::ParsedDicomDir& dicomDir, + Orthanc::ResourceType level, + size_t index, + const Orthanc::DicomMap& parent) + { + std::string expectedType; + + switch (level) + { + case Orthanc::ResourceType_Patient: + expectedType = "PATIENT"; + break; + + case Orthanc::ResourceType_Study: + expectedType = "STUDY"; + break; + + case Orthanc::ResourceType_Series: + expectedType = "SERIES"; + break; + + case Orthanc::ResourceType_Instance: + expectedType = "IMAGE"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + for (;;) + { + std::unique_ptr current(dicomDir.GetItem(index).Clone()); + current->RemoveBinaryTags(); + current->Merge(parent); + + std::string type; + if (!current->LookupStringValue(type, Orthanc::DICOM_TAG_DIRECTORY_RECORD_TYPE, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (type == expectedType) + { + if (level == Orthanc::ResourceType_Instance) + { + instances.AddResource(*current); + } + else + { + size_t lower; + if (dicomDir.LookupLower(lower, index)) + { + ExploreDicomDir(instances, dicomDir, Orthanc::GetChildResourceType(level), lower, *current); + } + } + } + + size_t next; + if (dicomDir.LookupNext(next, index)) + { + index = next; + } + else + { + return; + } + } + } +#endif + + +#if ORTHANC_ENABLE_DCMTK == 1 + void DicomResourcesLoader::GetDicomDirInstances(LoadedDicomResources& target, + const Orthanc::ParsedDicomDir& dicomDir) + { + Orthanc::DicomMap parent; + ExploreDicomDir(target, dicomDir, Orthanc::ResourceType_Patient, 0, parent); + } +#endif + + +#if ORTHANC_ENABLE_DCMTK == 1 + class DicomResourcesLoader::DicomDirHandler : public StringHandler + { + public: + DicomDirHandler(boost::shared_ptr loader, + boost::shared_ptr target, + int priority, + const DicomSource& source, + boost::shared_ptr userPayload) : + StringHandler(loader, target, priority, source, userPayload) + { + } + + virtual void HandleJson(const Json::Value& body) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + virtual void HandleString(const std::string& body) + { + Orthanc::ParsedDicomDir dicomDir(body); + GetDicomDirInstances(*GetTarget(), dicomDir); + BroadcastSuccess(); + } + }; +#endif + + + void DicomResourcesLoader::Handle(const HttpCommand::SuccessMessage& message) + { + if (message.GetOrigin().HasPayload()) + { + dynamic_cast(message.GetOrigin().GetPayload()).HandleString(message.GetAnswer()); + } + } + + + void DicomResourcesLoader::Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + if (message.GetOrigin().HasPayload()) + { + dynamic_cast(message.GetOrigin().GetPayload()).HandleString(message.GetAnswer()); + } + } + + + void DicomResourcesLoader::Handle(const ReadFileCommand::SuccessMessage& message) + { + if (message.GetOrigin().HasPayload()) + { + dynamic_cast(message.GetOrigin().GetPayload()).HandleString(message.GetContent()); + } + } + + +#if ORTHANC_ENABLE_DCMTK == 1 + void DicomResourcesLoader::Handle(const ParseDicomSuccessMessage& message) + { + if (message.GetOrigin().HasPayload()) + { + Handler& handler = dynamic_cast(message.GetOrigin().GetPayload()); + + std::set ignoreTagLength; + ignoreTagLength.insert(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR); // Needed for RT-DOSE + + Orthanc::DicomMap summary; + message.GetDicom().ExtractDicomSummary(summary, ignoreTagLength); + handler.GetTarget()->AddResource(summary); + + handler.BroadcastSuccess(); + } + } +#endif + + + void DicomResourcesLoader::Handle(const OracleCommandExceptionMessage& message) + { + // TODO + LOG(ERROR) << "Exception: " << message.GetException().What(); + } + + + void DicomResourcesLoader::ScheduleLoadOrthancInstanceTags(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& instanceId, + boost::shared_ptr remainingCommands, + boost::shared_ptr userPayload) + { + std::unique_ptr command(new OrthancRestApiCommand); + command->SetUri("/instances/" + instanceId + "/tags"); + command->AcquirePayload(new OrthancInstanceTagsHandler(shared_from_this(), target, priority, + source, remainingCommands, userPayload)); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + + + void DicomResourcesLoader::ScheduleLoadOrthancOneChildInstance(boost::shared_ptr target, + int priority, + const DicomSource& source, + Orthanc::ResourceType level, + const std::string& id, + boost::shared_ptr remainingCommands, + boost::shared_ptr userPayload) + { + std::unique_ptr command(new OrthancRestApiCommand); + command->SetUri("/" + GetUri(level) + "/" + id + "/instances"); + command->AcquirePayload(new OrthancOneChildInstanceHandler(shared_from_this(), target, priority, + source, remainingCommands, userPayload)); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + + + + const Orthanc::IDynamicObject& DicomResourcesLoader::SuccessMessage::GetUserPayload() const + { + if (userPayload_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *userPayload_; + } + } + + + boost::shared_ptr DicomResourcesLoader::Factory::Create(ILoadersContext::ILock& stone) + { + boost::shared_ptr result(new DicomResourcesLoader(stone.GetContext())); + result->Register(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); + result->Register(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); + result->Register(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); + result->Register(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); + +#if ORTHANC_ENABLE_DCMTK == 1 + result->Register(stone.GetOracleObservable(), &DicomResourcesLoader::Handle); +#endif + + return boost::shared_ptr(result); + } + + + static void SetIncludeTags(std::map& arguments, + const std::set& includeTags) + { + if (!includeTags.empty()) + { + std::string s; + bool first = true; + + for (std::set::const_iterator + it = includeTags.begin(); it != includeTags.end(); ++it) + { + if (first) + { + first = false; + } + else + { + s += ","; + } + + char buf[16]; + sprintf(buf, "%04X%04X", it->GetGroup(), it->GetElement()); + s += std::string(buf); + } + + arguments["includefield"] = s; + } + } + + + void DicomResourcesLoader::ScheduleGetDicomWeb(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& uri, + const std::set& includeTags, + Orthanc::IDynamicObject* userPayload) + { + boost::shared_ptr protection(userPayload); + + if (!source.IsDicomWeb()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, "Not a DICOMweb source"); + } + + std::map arguments, headers; + SetIncludeTags(arguments, includeTags); + + std::unique_ptr command( + source.CreateDicomWebCommand(uri, arguments, headers, + new DicomWebHandler(shared_from_this(), target, priority, source, protection))); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + + + void DicomResourcesLoader::ScheduleQido(boost::shared_ptr target, + int priority, + const DicomSource& source, + Orthanc::ResourceType level, + const Orthanc::DicomMap& filter, + const std::set& includeTags, + Orthanc::IDynamicObject* userPayload) + { + boost::shared_ptr protection(userPayload); + + if (!source.IsDicomWeb()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, "Not a DICOMweb source"); + } + + std::string uri; + switch (level) + { + case Orthanc::ResourceType_Study: + uri = "/studies"; + break; + + case Orthanc::ResourceType_Series: + uri = "/series"; + break; + + case Orthanc::ResourceType_Instance: + uri = "/instances"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::set tags; + filter.GetTags(tags); + + std::map arguments, headers; + + for (std::set::const_iterator it = tags.begin(); it != tags.end(); ++it) + { + std::string s; + if (filter.LookupStringValue(s, *it, false /* no binary */)) + { + char buf[16]; + sprintf(buf, "%04X%04X", it->GetGroup(), it->GetElement()); + arguments[buf] = s; + } + } + + SetIncludeTags(arguments, includeTags); + + std::unique_ptr command( + source.CreateDicomWebCommand(uri, arguments, headers, + new DicomWebHandler(shared_from_this(), target, priority, source, protection))); + + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + + + void DicomResourcesLoader::ScheduleLoadOrthancResources(boost::shared_ptr target, + int priority, + const DicomSource& source, + Orthanc::ResourceType topLevel, + const std::string& topId, + Orthanc::ResourceType bottomLevel, + Orthanc::IDynamicObject* userPayload) + { + boost::shared_ptr protection(userPayload); + + if (!source.IsOrthanc()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, "Not an Orthanc source"); + } + + bool ok = false; + + switch (topLevel) + { + case Orthanc::ResourceType_Patient: + ok = (bottomLevel == Orthanc::ResourceType_Patient || + bottomLevel == Orthanc::ResourceType_Study || + bottomLevel == Orthanc::ResourceType_Series || + bottomLevel == Orthanc::ResourceType_Instance); + break; + + case Orthanc::ResourceType_Study: + ok = (bottomLevel == Orthanc::ResourceType_Study || + bottomLevel == Orthanc::ResourceType_Series || + bottomLevel == Orthanc::ResourceType_Instance); + break; + + case Orthanc::ResourceType_Series: + ok = (bottomLevel == Orthanc::ResourceType_Series || + bottomLevel == Orthanc::ResourceType_Instance); + break; + + case Orthanc::ResourceType_Instance: + ok = (bottomLevel == Orthanc::ResourceType_Instance); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (!ok) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + boost::shared_ptr remainingCommands(new unsigned int(0)); + + if (topLevel == Orthanc::ResourceType_Instance) + { + ScheduleLoadOrthancInstanceTags(target, priority, source, topId, remainingCommands, protection); + } + else if (topLevel == bottomLevel) + { + ScheduleLoadOrthancOneChildInstance(target, priority, source, topLevel, topId, remainingCommands, protection); + } + else + { + std::unique_ptr command(new OrthancRestApiCommand); + command->SetUri("/" + GetUri(topLevel) + "/" + topId + "/" + GetUri(bottomLevel)); + command->AcquirePayload(new OrthancAllChildrenInstancesHandler + (shared_from_this(), target, priority, source, + remainingCommands, bottomLevel, protection)); + + { + std::unique_ptr lock(context_.Lock()); + + // GetSharedObserver() means "this" (for use as an IObserver), as a + // shared_ptr + // The oracle will thus call "this" + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + } + + + void DicomResourcesLoader::ScheduleLoadDicomDir(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& path, + Orthanc::IDynamicObject* userPayload) + { + boost::shared_ptr protection(userPayload); + + if (!source.IsDicomDir()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, "Not a DICOMDIR source"); + } + + if (target->GetIndexedTag() == Orthanc::DICOM_TAG_SOP_INSTANCE_UID) + { + LOG(WARNING) << "If loading DICOMDIR, it is advised to index tag " + << "ReferencedSopInstanceUidInFile (0004,1511)"; + } + +#if ORTHANC_ENABLE_DCMTK == 1 + std::unique_ptr command(new ReadFileCommand(path)); + command->AcquirePayload(new DicomDirHandler(shared_from_this(), target, priority, source, protection)); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "DCMTK is disabled, cannot load DICOMDIR"); +#endif + } + + + void DicomResourcesLoader::ScheduleLoadDicomFile(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& path, + bool includePixelData, + Orthanc::IDynamicObject* userPayload) + { + boost::shared_ptr protection(userPayload); + +#if ORTHANC_ENABLE_DCMTK == 1 + std::unique_ptr command(new ParseDicomFromFileCommand(path)); + command->SetPixelDataIncluded(includePixelData); + command->AcquirePayload(new Handler(shared_from_this(), target, priority, source, protection)); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "DCMTK is disabled, cannot load DICOM files"); +#endif + } + + + bool DicomResourcesLoader::ScheduleLoadDicomFile(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& dicomDirPath, + const Orthanc::DicomMap& dicomDirEntry, + bool includePixelData, + Orthanc::IDynamicObject* userPayload) + { + std::unique_ptr protection(userPayload); + +#if ORTHANC_ENABLE_DCMTK == 1 + std::string file; + if (dicomDirEntry.LookupStringValue(file, Orthanc::DICOM_TAG_REFERENCED_FILE_ID, false)) + { + ScheduleLoadDicomFile(target, priority, source, ParseDicomFromFileCommand::GetDicomDirPath(dicomDirPath, file), + includePixelData, protection.release()); + return true; + } + else + { + return false; + } +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "DCMTK is disabled, cannot load DICOM files"); +#endif + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/DicomResourcesLoader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/DicomResourcesLoader.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,220 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "../Oracle/ParseDicomFromFileCommand.h" +# include +#endif + +#include "../Oracle/HttpCommand.h" +#include "../Oracle/OracleCommandExceptionMessage.h" +#include "../Oracle/OrthancRestApiCommand.h" +#include "../Oracle/ReadFileCommand.h" +#include "DicomSource.h" +#include "ILoaderFactory.h" +#include "LoadedDicomResources.h" +#include "OracleScheduler.h" + +namespace OrthancStone +{ + class DicomResourcesLoader : + public ObserverBase, + public IObservable + { + private: + class Handler; + class StringHandler; + class DicomWebHandler; + class OrthancHandler; + class OrthancInstanceTagsHandler; + class OrthancOneChildInstanceHandler; + class OrthancAllChildrenInstancesHandler; + +#if ORTHANC_ENABLE_DCMTK == 1 + class DicomDirHandler; +#endif + + void Handle(const HttpCommand::SuccessMessage& message); + + void Handle(const OrthancRestApiCommand::SuccessMessage& message); + + void Handle(const ReadFileCommand::SuccessMessage& message); + + void Handle(const OracleCommandExceptionMessage& message); + +#if ORTHANC_ENABLE_DCMTK == 1 + void Handle(const ParseDicomSuccessMessage& message); +#endif + + void ScheduleLoadOrthancInstanceTags(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& instanceId, + boost::shared_ptr remainingCommands, + boost::shared_ptr userPayload); + + void ScheduleLoadOrthancOneChildInstance(boost::shared_ptr target, + int priority, + const DicomSource& source, + Orthanc::ResourceType level, + const std::string& id, + boost::shared_ptr remainingCommands, + boost::shared_ptr userPayload); + + DicomResourcesLoader(ILoadersContext& context) : + context_(context) + { + } + + ILoadersContext& context_; + + + public: + class SuccessMessage : public OrthancStone::OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + boost::shared_ptr resources_; + int priority_; + const DicomSource& source_; + const Orthanc::IDynamicObject* userPayload_; + + public: + SuccessMessage(const DicomResourcesLoader& origin, + boost::shared_ptr resources, + int priority, + const DicomSource& source, + const Orthanc::IDynamicObject* userPayload) : + OriginMessage(origin), + resources_(resources), + priority_(priority), + source_(source), + userPayload_(userPayload) + { + } + + int GetPriority() const + { + return priority_; + } + + const boost::shared_ptr GetResources() const + { + return resources_; + } + + const DicomSource& GetDicomSource() const + { + return source_; + } + + bool HasUserPayload() const + { + return userPayload_ != NULL; + } + + const Orthanc::IDynamicObject& GetUserPayload() const; + }; + + + class Factory : public ILoaderFactory + { + public: + virtual boost::shared_ptr Create(ILoadersContext::ILock& stone); + }; + + void ScheduleGetDicomWeb(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& uri, + const std::set& includeTags, + Orthanc::IDynamicObject* userPayload); + + void ScheduleGetDicomWeb(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& uri, + Orthanc::IDynamicObject* userPayload) + { + std::set includeTags; + ScheduleGetDicomWeb(target, priority, source, uri, includeTags, userPayload); + } + + void ScheduleQido(boost::shared_ptr target, + int priority, + const DicomSource& source, + Orthanc::ResourceType level, + const Orthanc::DicomMap& filter, + const std::set& includeTags, + Orthanc::IDynamicObject* userPayload); + + void ScheduleLoadOrthancResources(boost::shared_ptr target, + int priority, + const DicomSource& source, + Orthanc::ResourceType topLevel, + const std::string& topId, + Orthanc::ResourceType bottomLevel, + Orthanc::IDynamicObject* userPayload); + + void ScheduleLoadOrthancResource(boost::shared_ptr target, + int priority, + const DicomSource& source, + Orthanc::ResourceType level, + const std::string& id, + Orthanc::IDynamicObject* userPayload) + { + ScheduleLoadOrthancResources(target, priority, source, level, id, level, userPayload); + } + +#if ORTHANC_ENABLE_DCMTK == 1 + static void GetDicomDirInstances(LoadedDicomResources& target, + const Orthanc::ParsedDicomDir& dicomDir); +#endif + + void ScheduleLoadDicomDir(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& path, + Orthanc::IDynamicObject* userPayload); + + void ScheduleLoadDicomFile(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& path, + bool includePixelData, + Orthanc::IDynamicObject* userPayload); + + bool ScheduleLoadDicomFile(boost::shared_ptr target, + int priority, + const DicomSource& source, + const std::string& dicomDirPath, + const Orthanc::DicomMap& dicomDirEntry, + bool includePixelData, + Orthanc::IDynamicObject* userPayload); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/DicomSource.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/DicomSource.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "DicomSource.h" + +#include "../Oracle/HttpCommand.h" +#include "../Oracle/OrthancRestApiCommand.h" + +#include + +#include + +namespace OrthancStone +{ + static std::string EncodeGetArguments(const std::string& uri, + const std::map& arguments) + { + std::string s = uri; + bool first = true; + + for (std::map::const_iterator + it = arguments.begin(); it != arguments.end(); ++it) + { + if (first) + { + s += "?"; + first = false; + } + else + { + s += "&"; + } + + s += it->first + "=" + it->second; + } + + // TODO: Call Orthanc::Toolbox::UriEncode() ? + + return s; + } + + + void DicomSource::SetOrthancSource(const Orthanc::WebServiceParameters& parameters) + { + type_ = DicomSourceType_Orthanc; + webService_ = parameters; + hasOrthancWebViewer1_ = false; + hasOrthancAdvancedPreview_ = false; + } + + + void DicomSource::SetOrthancSource() + { + Orthanc::WebServiceParameters parameters; + parameters.SetUrl("http://localhost:8042/"); + SetOrthancSource(parameters); + } + + + const Orthanc::WebServiceParameters& DicomSource::GetOrthancParameters() const + { + if (type_ == DicomSourceType_Orthanc || + type_ == DicomSourceType_DicomWebThroughOrthanc) + { + return webService_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void DicomSource::SetDicomDirSource() + { + type_ = DicomSourceType_DicomDir; + } + + + void DicomSource::SetDicomWebSource(const std::string& baseUrl) + { + type_ = DicomSourceType_DicomWeb; + webService_.SetUrl(baseUrl); + webService_.ClearCredentials(); + } + + + void DicomSource::SetDicomWebSource(const std::string& baseUrl, + const std::string& username, + const std::string& password) + { + type_ = DicomSourceType_DicomWeb; + webService_.SetUrl(baseUrl); + webService_.SetCredentials(username, password); + } + + + void DicomSource::SetDicomWebThroughOrthancSource(const Orthanc::WebServiceParameters& orthancParameters, + const std::string& dicomWebRoot, + const std::string& serverName) + { + type_ = DicomSourceType_DicomWebThroughOrthanc; + webService_ = orthancParameters; + orthancDicomWebRoot_ = dicomWebRoot; + serverName_ = serverName; + } + + + void DicomSource::SetDicomWebThroughOrthancSource(const std::string& serverName) + { + Orthanc::WebServiceParameters orthanc; + orthanc.SetUrl("http://localhost:8042/"); + SetDicomWebThroughOrthancSource(orthanc, "/dicom-web/", serverName); + } + + + bool DicomSource::IsDicomWeb() const + { + return (type_ == DicomSourceType_DicomWeb || + type_ == DicomSourceType_DicomWebThroughOrthanc); + } + + + IOracleCommand* DicomSource::CreateDicomWebCommand(const std::string& uri, + const std::map& arguments, + const std::map& headers, + Orthanc::IDynamicObject* payload) const + { + std::unique_ptr protection(payload); + + switch (type_) + { + case DicomSourceType_DicomWeb: + { + std::unique_ptr command(new HttpCommand); + + command->SetMethod(Orthanc::HttpMethod_Get); + command->SetUrl(webService_.GetUrl() + "/" + EncodeGetArguments(uri, arguments)); + command->SetHttpHeaders(webService_.GetHttpHeaders()); + + for (std::map::const_iterator + it = headers.begin(); it != headers.end(); ++it) + { + command->SetHttpHeader(it->first, it->second); + } + + if (!webService_.GetUsername().empty()) + { + command->SetCredentials(webService_.GetUsername(), webService_.GetPassword()); + } + + if (protection.get()) + { + command->AcquirePayload(protection.release()); + } + + return command.release(); + } + + case DicomSourceType_DicomWebThroughOrthanc: + { + Json::Value args = Json::objectValue; + for (std::map::const_iterator + it = arguments.begin(); it != arguments.end(); ++it) + { + args[it->first] = it->second; + } + + Json::Value h = Json::objectValue; + for (std::map::const_iterator + it = headers.begin(); it != headers.end(); ++it) + { + h[it->first] = it->second; + } + + Json::Value body = Json::objectValue; + body["Uri"] = uri; + body["Arguments"] = args; + body["Headers"] = h; + + std::unique_ptr command(new OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetUri(orthancDicomWebRoot_ + "/servers/" + serverName_ + "/get"); + command->SetBody(body); + + if (protection.get()) + { + command->AcquirePayload(protection.release()); + } + + return command.release(); + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void DicomSource::AutodetectOrthancFeatures(const std::string& system, + const std::string& plugins) + { + static const char* const REST_API_VERSION = "ApiVersion"; + + if (IsDicomWeb()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + Json::Value a, b; + Json::Reader reader; + if (reader.parse(system, a) && + reader.parse(plugins, b) && + a.type() == Json::objectValue && + b.type() == Json::arrayValue && + a.isMember(REST_API_VERSION) && + a[REST_API_VERSION].type() == Json::intValue) + { + SetOrthancAdvancedPreview(a[REST_API_VERSION].asInt() >= 5); + + hasOrthancWebViewer1_ = false; + + for (Json::Value::ArrayIndex i = 0; i < b.size(); i++) + { + if (b[i].type() != Json::stringValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (boost::iequals(b[i].asString(), "web-viewer")) + { + hasOrthancWebViewer1_ = true; + } + } + } + else + { + printf("[%s] [%s]\n", system.c_str(), plugins.c_str()); + + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + void DicomSource::SetOrthancWebViewer1(bool hasPlugin) + { + if (IsOrthanc()) + { + hasOrthancWebViewer1_ = hasPlugin; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + bool DicomSource::HasOrthancWebViewer1() const + { + if (IsOrthanc()) + { + return hasOrthancWebViewer1_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + void DicomSource::SetOrthancAdvancedPreview(bool hasFeature) + { + if (IsOrthanc()) + { + hasOrthancAdvancedPreview_ = hasFeature; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + bool DicomSource::HasOrthancAdvancedPreview() const + { + if (IsOrthanc()) + { + return hasOrthancAdvancedPreview_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + void DicomSource::SetDicomWebRendered(bool hasFeature) + { + if (IsDicomWeb()) + { + hasDicomWebRendered_ = hasFeature; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + bool DicomSource::HasDicomWebRendered() const + { + if (IsDicomWeb()) + { + return hasDicomWebRendered_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + unsigned int DicomSource::GetQualityCount() const + { + if (IsDicomWeb()) + { + return (HasDicomWebRendered() ? 2 : 1); + } + else if (IsOrthanc()) + { + return (HasOrthancWebViewer1() || + HasOrthancAdvancedPreview() ? 2 : 1); + } + else if (IsDicomDir()) + { + return 1; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/DicomSource.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/DicomSource.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Oracle/IOracleCommand.h" + +#include + +namespace OrthancStone +{ + enum DicomSourceType + { + DicomSourceType_Orthanc, + DicomSourceType_DicomWeb, + DicomSourceType_DicomWebThroughOrthanc, + DicomSourceType_DicomDir + }; + + + class DicomSource + { + private: + DicomSourceType type_; + Orthanc::WebServiceParameters webService_; + std::string orthancDicomWebRoot_; + std::string serverName_; + bool hasOrthancWebViewer1_; + bool hasOrthancAdvancedPreview_; + bool hasDicomWebRendered_; + + public: + DicomSource() : + hasOrthancWebViewer1_(false), + hasOrthancAdvancedPreview_(false), + hasDicomWebRendered_(false) + { + SetOrthancSource(); + } + + DicomSourceType GetType() const + { + return type_; + } + + void SetOrthancSource(); + + void SetOrthancSource(const Orthanc::WebServiceParameters& parameters); + + const Orthanc::WebServiceParameters& GetOrthancParameters() const; + + void SetDicomDirSource(); + + void SetDicomWebSource(const std::string& baseUrl); + + void SetDicomWebSource(const std::string& baseUrl, + const std::string& username, + const std::string& password); + + void SetDicomWebThroughOrthancSource(const Orthanc::WebServiceParameters& orthancParameters, + const std::string& dicomWebRoot, + const std::string& serverName); + + void SetDicomWebThroughOrthancSource(const std::string& serverName); + + bool IsDicomWeb() const; + + bool IsOrthanc() const + { + return type_ == DicomSourceType_Orthanc; + } + + bool IsDicomDir() const + { + return type_ == DicomSourceType_DicomDir; + } + + IOracleCommand* CreateDicomWebCommand(const std::string& uri, + const std::map& arguments, + const std::map& headers, + Orthanc::IDynamicObject* payload /* takes ownership */) const; + + void AutodetectOrthancFeatures(const std::string& system, + const std::string& plugins); + + void SetOrthancWebViewer1(bool hasPlugin); + + bool HasOrthancWebViewer1() const; + + void SetOrthancAdvancedPreview(bool hasFeature); + + bool HasOrthancAdvancedPreview() const; + + void SetDicomWebRendered(bool hasFeature); + + bool HasDicomWebRendered() const; + + unsigned int GetQualityCount() const; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/DicomStructureSetLoader.cpp --- a/Framework/Loaders/DicomStructureSetLoader.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Loaders/DicomStructureSetLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -29,11 +29,6 @@ #include -#if 0 -bool logbgo233 = false; -bool logbgo115 = false; -#endif - namespace OrthancStone { @@ -67,7 +62,7 @@ { } - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { Json::Value tags; message.ParseJsonBody(tags); @@ -78,10 +73,12 @@ DicomStructureSetLoader& loader = GetLoader(); loader.content_->AddReferencedSlice(dicom); - loader.countProcessedInstances_ ++; assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_); + loader.revision_++; + loader.SetStructuresUpdated(); + if (loader.countProcessedInstances_ == loader.countReferencedInstances_) { // All the referenced instances have been loaded, finalize the RT-STRUCT @@ -107,7 +104,7 @@ { } - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { #if 0 LOG(TRACE) << "DicomStructureSetLoader::LookupInstance::Handle() (SUCCESS)"; @@ -127,7 +124,7 @@ { std::stringstream msg; msg << "Unknown resource! message.GetAnswer() = " << message.GetAnswer() << " message.GetAnswerHeaders() = "; - for (OrthancRestApiCommand::HttpHeaders::const_iterator it = message.GetAnswerHeaders().begin(); + for (OrthancStone::OrthancRestApiCommand::HttpHeaders::const_iterator it = message.GetAnswerHeaders().begin(); it != message.GetAnswerHeaders().end(); ++it) { msg << "\nkey: \"" << it->first << "\" value: \"" << it->second << "\"\n"; @@ -140,11 +137,11 @@ const std::string instanceId = lookup[0]["ID"].asString(); { - std::unique_ptr command(new OrthancRestApiCommand); + std::unique_ptr command(new OrthancStone::OrthancRestApiCommand); command->SetHttpHeader("Accept-Encoding", "gzip"); std::string uri = "/instances/" + instanceId + "/tags"; command->SetUri(uri); - command->SetPayload(new AddReferencedInstance(loader, instanceId)); + command->AcquirePayload(new AddReferencedInstance(loader, instanceId)); Schedule(command.release()); } } @@ -159,17 +156,13 @@ { } - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { -#if 0 - if (logbgo115) - LOG(TRACE) << "DicomStructureSetLoader::LoadStructure::Handle() (SUCCESS)"; -#endif DicomStructureSetLoader& loader = GetLoader(); { OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer()); - loader.content_.reset(new DicomStructureSet(dicom)); + loader.content_.reset(new OrthancStone::DicomStructureSet(dicom)); size_t structureCount = loader.content_->GetStructuresCount(); loader.structureVisibility_.resize(structureCount); bool everythingVisible = false; @@ -227,11 +220,11 @@ for (std::set::const_iterator it = nonEmptyInstances.begin(); it != nonEmptyInstances.end(); ++it) { - std::unique_ptr command(new OrthancRestApiCommand); + std::unique_ptr command(new OrthancStone::OrthancRestApiCommand); command->SetUri("/tools/lookup"); command->SetMethod(Orthanc::HttpMethod_Post); command->SetBody(*it); - command->SetPayload(new LookupInstance(loader, *it)); + command->AcquirePayload(new LookupInstance(loader, *it)); Schedule(command.release()); } } @@ -241,7 +234,7 @@ class DicomStructureSetLoader::Slice : public IExtractedSlice { private: - const DicomStructureSet& content_; + const OrthancStone::DicomStructureSet& content_; uint64_t revision_; bool isValid_; std::vector visibility_; @@ -257,9 +250,9 @@ In the second case, the visibility of each structure is defined by the content of the vector at the corresponding index. */ - Slice(const DicomStructureSet& content, + Slice(const OrthancStone::DicomStructureSet& content, uint64_t revision, - const CoordinateSystem3D& cuttingPlane, + const OrthancStone::CoordinateSystem3D& cuttingPlane, std::vector visibility = std::vector()) : content_(content) , revision_(revision) @@ -270,11 +263,11 @@ bool opposite; - const Vector normal = content.GetNormal(); + const OrthancStone::Vector normal = content.GetNormal(); isValid_ = ( - GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) || - GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) || - GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY())); + OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) || + OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) || + OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY())); } virtual bool IsValid() @@ -287,22 +280,23 @@ return revision_; } - virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, - const CoordinateSystem3D& cuttingPlane) + virtual OrthancStone::ISceneLayer* CreateSceneLayer( + const OrthancStone::ILayerStyleConfigurator* configurator, + const OrthancStone::CoordinateSystem3D& cuttingPlane) { assert(isValid_); - std::unique_ptr layer(new PolylineSceneLayer); + std::unique_ptr layer(new OrthancStone::PolylineSceneLayer); layer->SetThickness(2); for (size_t i = 0; i < content_.GetStructuresCount(); i++) { if ((visibility_.size() == 0) || visibility_.at(i)) { - const Color& color = content_.GetStructureColor(i); + const OrthancStone::Color& color = content_.GetStructureColor(i); #ifdef USE_BOOST_UNION_FOR_POLYGONS - std::vector< std::vector > polygons; + std::vector< std::vector > polygons; if (content_.ProjectStructure(polygons, i, cuttingPlane)) { @@ -320,17 +314,17 @@ } } #else - std::vector< std::pair > segments; + std::vector< std::pair > segments; if (content_.ProjectStructure(segments, i, cuttingPlane)) { for (size_t j = 0; j < segments.size(); j++) { - PolylineSceneLayer::Chain chain; + OrthancStone::PolylineSceneLayer::Chain chain; chain.resize(2); - chain[0] = ScenePoint2D(segments[j].first.x, segments[j].first.y); - chain[1] = ScenePoint2D(segments[j].second.x, segments[j].second.y); + chain[0] = OrthancStone::ScenePoint2D(segments[j].first.x, segments[j].first.y); + chain[1] = OrthancStone::ScenePoint2D(segments[j].second.x, segments[j].second.y); layer->AddChain(chain, false /* NOT closed */, color); } @@ -344,18 +338,28 @@ }; - DicomStructureSetLoader::DicomStructureSetLoader(IOracle& oracle, - IObservable& oracleObservable) : - LoaderStateMachine(oracle, oracleObservable), - IObservable(oracleObservable.GetBroker()), - revision_(0), - countProcessedInstances_(0), - countReferencedInstances_(0), - structuresReady_(false) + DicomStructureSetLoader::DicomStructureSetLoader( + OrthancStone::ILoadersContext& loadersContext) + : LoaderStateMachine(loadersContext) + , loadersContext_(loadersContext) + , revision_(0) + , countProcessedInstances_(0) + , countReferencedInstances_(0) + , structuresReady_(false) { } + - + boost::shared_ptr DicomStructureSetLoader::Create(OrthancStone::ILoadersContext& loadersContext) + { + boost::shared_ptr obj( + new DicomStructureSetLoader( + loadersContext)); + obj->LoaderStateMachine::PostConstructor(); + return obj; + + } + void DicomStructureSetLoader::SetStructureDisplayState(size_t structureIndex, bool display) { structureVisibility_.at(structureIndex) = display; @@ -377,24 +381,24 @@ initiallyVisibleStructures_ = initiallyVisibleStructures; { - std::unique_ptr command(new OrthancRestApiCommand); + std::unique_ptr command(new OrthancStone::OrthancRestApiCommand); command->SetHttpHeader("Accept-Encoding", "gzip"); std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; command->SetUri(uri); - command->SetPayload(new LoadStructure(*this)); + command->AcquirePayload(new LoadStructure(*this)); Schedule(command.release()); } } - IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane) + OrthancStone::IVolumeSlicer::IExtractedSlice* DicomStructureSetLoader::ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) { if (content_.get() == NULL) { // Geometry is not available yet - return new IVolumeSlicer::InvalidSlice; + return new OrthancStone::IVolumeSlicer::InvalidSlice; } else { @@ -402,6 +406,11 @@ } } + void DicomStructureSetLoader::SetStructuresUpdated() + { + BroadcastMessage(DicomStructureSetLoader::StructuresUpdated(*this)); + } + void DicomStructureSetLoader::SetStructuresReady() { ORTHANC_ASSERT(!structuresReady_); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/DicomStructureSetLoader.h --- a/Framework/Loaders/DicomStructureSetLoader.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Loaders/DicomStructureSetLoader.h Wed Apr 22 14:05:47 2020 +0200 @@ -23,6 +23,7 @@ #include "../Toolbox/DicomStructureSet.h" #include "../Volumes/IVolumeSlicer.h" +#include "../Loaders/ILoadersContext.h" #include "LoaderStateMachine.h" #include @@ -31,8 +32,8 @@ { class DicomStructureSetLoader : public LoaderStateMachine, - public IVolumeSlicer, - public IObservable + public OrthancStone::IVolumeSlicer, + public OrthancStone::IObservable { private: class Slice; @@ -42,14 +43,15 @@ class LookupInstance; // 2nd state class LoadStructure; // 1st state - std::unique_ptr content_; - uint64_t revision_; - std::string instanceId_; - unsigned int countProcessedInstances_; - unsigned int countReferencedInstances_; + OrthancStone::ILoadersContext& loadersContext_; + std::unique_ptr content_; + uint64_t revision_; + std::string instanceId_; + unsigned int countProcessedInstances_; + unsigned int countReferencedInstances_; // will be set to true once the loading is finished - bool structuresReady_; + bool structuresReady_; /** At load time, these strings are used to initialize the structureVisibility_ @@ -68,13 +70,17 @@ */ std::vector structureVisibility_; + protected: + DicomStructureSetLoader(OrthancStone::ILoadersContext& loadersContext); + public: ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader); + ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresUpdated, DicomStructureSetLoader); - DicomStructureSetLoader(IOracle& oracle, - IObservable& oracleObservable); - - DicomStructureSet* GetContent() + static boost::shared_ptr Create( + OrthancStone::ILoadersContext& loadersContext); + + OrthancStone::DicomStructureSet* GetContent() { return content_.get(); } @@ -91,9 +97,10 @@ void LoadInstance(const std::string& instanceId, const std::vector& initiallyVisibleStructures = std::vector()); - virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; + virtual IExtractedSlice* ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; void SetStructuresReady(); + void SetStructuresUpdated(); bool AreStructuresReady() const; }; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/DicomStructureSetLoader2.cpp --- a/Framework/Loaders/DicomStructureSetLoader2.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "DicomStructureSetLoader2.h" - -#include "../Messages/IObservable.h" -#include "../Oracle/IOracle.h" -#include "../Oracle/OracleCommandExceptionMessage.h" - -namespace OrthancStone -{ - - DicomStructureSetLoader2::DicomStructureSetLoader2( - DicomStructureSet2& structureSet - , IOracle& oracle - , IObservable& oracleObservable) - : IObserver(oracleObservable.GetBroker()) - , IObservable(oracleObservable.GetBroker()) - , structureSet_(structureSet) - , oracle_(oracle) - , oracleObservable_(oracleObservable) - , structuresReady_(false) - { - LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::DicomStructureSetLoader2()"; - - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &DicomStructureSetLoader2::HandleSuccessMessage)); - - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &DicomStructureSetLoader2::HandleExceptionMessage)); - } - - DicomStructureSetLoader2::~DicomStructureSetLoader2() - { - LOG(TRACE) << "DicomStructureSetLoader2(" << std::hex << this << std::dec << ")::~DicomStructureSetLoader2()"; - oracleObservable_.Unregister(this); - } - - void DicomStructureSetLoader2::LoadInstanceFromString(const std::string& body) - { - OrthancPlugins::FullOrthancDataset dicom(body); - //loader.content_.reset(new DicomStructureSet(dicom)); - structureSet_.Clear(); - structureSet_.SetContents(dicom); - SetStructuresReady(); - } - - void DicomStructureSetLoader2::HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message) - { - const std::string& body = message.GetAnswer(); - LoadInstanceFromString(body); - } - - void DicomStructureSetLoader2::HandleExceptionMessage(const OracleCommandExceptionMessage& message) - { - LOG(ERROR) << "DicomStructureSetLoader2::HandleExceptionMessage: error when trying to load data. " - << "Error: " << message.GetException().What() << " Details: " - << message.GetException().GetDetails(); - } - - void DicomStructureSetLoader2::LoadInstance(const std::string& instanceId) - { - std::unique_ptr command(new OrthancRestApiCommand); - command->SetHttpHeader("Accept-Encoding", "gzip"); - - std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; - - command->SetUri(uri); - oracle_.Schedule(*this, command.release()); - } - - void DicomStructureSetLoader2::SetStructuresReady() - { - structuresReady_ = true; - } - - bool DicomStructureSetLoader2::AreStructuresReady() const - { - return structuresReady_; - } - - /* - - void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message) - { - LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing"; - LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " << - message.GetException().GetDetails(); - Clear(); - } - - LoaderStateMachine::~LoaderStateMachine() - { - Clear(); - } - - - */ - -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/DicomStructureSetLoader2.h --- a/Framework/Loaders/DicomStructureSetLoader2.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - -#include "../Toolbox/DicomStructureSet2.h" -#include "../Messages/IMessage.h" -#include "../Messages/IObserver.h" -#include "../Messages/IObservable.h" -#include "../Oracle/OrthancRestApiCommand.h" - -#include - -namespace OrthancStone -{ - class IOracle; - class IObservable; - class OrthancRestApiCommand; - class OracleCommandExceptionMessage; - - class DicomStructureSetLoader2 : public IObserver, public IObservable - { - public: - ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, StructuresReady, DicomStructureSetLoader2); - - /** - Warning: the structureSet, oracle and oracleObservable objects must live - at least as long as this object (TODO: shared_ptr?) - */ - DicomStructureSetLoader2(DicomStructureSet2& structureSet, IOracle& oracle, IObservable& oracleObservable); - - ~DicomStructureSetLoader2(); - - void LoadInstance(const std::string& instanceId); - - /** Internal use */ - void LoadInstanceFromString(const std::string& body); - - void SetStructuresReady(); - bool AreStructuresReady() const; - - private: - /** - Called back by the oracle when data is ready! - */ - void HandleSuccessMessage(const OrthancRestApiCommand::SuccessMessage& message); - - /** - Called back by the oracle when shit hits the fan - */ - void HandleExceptionMessage(const OracleCommandExceptionMessage& message); - - /** - The structure set that will be (cleared and) filled with data from the - loader - */ - DicomStructureSet2& structureSet_; - - IOracle& oracle_; - IObservable& oracleObservable_; - bool structuresReady_; - }; -} - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/DicomVolumeLoader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/DicomVolumeLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,188 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "DicomVolumeLoader.h" + +#include + +namespace OrthancStone +{ + DicomVolumeLoader::DicomVolumeLoader(boost::shared_ptr& framesLoader, + bool computeRange) : + framesLoader_(framesLoader), + isValid_(false), + started_(false), + remaining_(0) + { + volume_.reset(new OrthancStone::DicomVolumeImage); + + const SeriesOrderedFrames& frames = framesLoader_->GetOrderedFrames(); + + if (frames.IsRegular3DVolume() && + frames.GetFramesCount() > 0) + { + // TODO - Is "0" the good choice for the reference frame? + // Shouldn't we use "count - 1" depending on the direction + // of the normal? + const OrthancStone::DicomInstanceParameters& parameters = frames.GetInstanceParameters(0); + + OrthancStone::CoordinateSystem3D plane(frames.GetInstance(0)); + + OrthancStone::VolumeImageGeometry geometry; + geometry.SetSizeInVoxels(parameters.GetImageInformation().GetWidth(), + parameters.GetImageInformation().GetHeight(), + static_cast(frames.GetFramesCount())); + geometry.SetAxialGeometry(plane); + + double spacing; + if (parameters.GetSopClassUid() == SopClassUid_RTDose) + { + if (!parameters.ComputeRegularSpacing(spacing)) + { + LOG(WARNING) << "Unable to compute the spacing in a RT-DOSE instance"; + spacing = frames.GetSpacingBetweenSlices(); + } + } + else + { + spacing = frames.GetSpacingBetweenSlices(); + } + + geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(), + parameters.GetPixelSpacingY(), spacing); + volume_->Initialize(geometry, parameters.GetExpectedPixelFormat(), computeRange); + volume_->GetPixelData().Clear(); + volume_->SetDicomParameters(parameters); + + remaining_ = frames.GetFramesCount(); + isValid_ = true; + } + else + { + LOG(WARNING) << "Not a regular 3D volume"; + } + } + + + void DicomVolumeLoader::Handle(const OrthancStone::SeriesFramesLoader::FrameLoadedMessage& message) + { + if (remaining_ == 0 || + !message.HasUserPayload()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (message.GetImage().GetWidth() != volume_->GetPixelData().GetWidth() || + message.GetImage().GetHeight() != volume_->GetPixelData().GetHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + if (message.GetImage().GetFormat() != volume_->GetPixelData().GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (message.GetFrameIndex() >= volume_->GetPixelData().GetDepth()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + size_t frameIndex = dynamic_cast&> + (message.GetUserPayload()).GetValue(); + + { + ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), + VolumeProjection_Axial, + static_cast(frameIndex)); + + Orthanc::ImageProcessing::Copy(writer.GetAccessor(), message.GetImage()); + } + + volume_->IncrementRevision(); + + { + VolumeUpdatedMessage updated(*this, + static_cast(frameIndex)); + + BroadcastMessage(updated); + } + + remaining_--; + + if (remaining_ == 0) + { + VolumeReadyMessage ready(*this); + BroadcastMessage(ready); + } + } + + + DicomVolumeLoader::Factory::Factory(LoadedDicomResources& instances) : + framesFactory_(instances), + computeRange_(false) + { + } + + DicomVolumeLoader::Factory::Factory(const SeriesMetadataLoader::SeriesLoadedMessage& metadata) : + framesFactory_(metadata.GetInstances()), + computeRange_(false) + { + SetDicomDir(metadata.GetDicomDirPath(), metadata.GetDicomDir()); // Only useful for DICOMDIR sources + } + + + boost::shared_ptr DicomVolumeLoader::Factory::Create(ILoadersContext::ILock& context) + { + boost::shared_ptr frames = + boost::dynamic_pointer_cast(framesFactory_.Create(context)); + + boost::shared_ptr volume(new DicomVolumeLoader(frames, computeRange_)); + volume->Register(*frames, &DicomVolumeLoader::Handle); + + return volume; + } + + void DicomVolumeLoader::Start(int priority, + const DicomSource& source) + { + if (started_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + started_ = true; + + if (IsValid()) + { + for (size_t i = 0; i < GetOrderedFrames().GetFramesCount(); i++) + { + framesLoader_->ScheduleLoadFrame(priority, source, i, source.GetQualityCount() - 1, + new Orthanc::SingleValueObject(i)); + } + } + else + { + VolumeReadyMessage ready(*this); + BroadcastMessage(ready); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/DicomVolumeLoader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/DicomVolumeLoader.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Volumes/DicomVolumeImage.h" +#include "SeriesFramesLoader.h" +#include "SeriesMetadataLoader.h" + +namespace OrthancStone +{ + class DicomVolumeLoader : + public ObserverBase, + public IObservable + { + private: + boost::shared_ptr framesLoader_; + boost::shared_ptr volume_; + bool isValid_; + bool started_; + size_t remaining_; + + DicomVolumeLoader(boost::shared_ptr& framesLoader, + bool computeRange); + + void Handle(const OrthancStone::SeriesFramesLoader::FrameLoadedMessage& message); + + public: + class VolumeReadyMessage : public OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + public: + VolumeReadyMessage(const DicomVolumeLoader& loader) : + OriginMessage(loader) + { + } + + const DicomVolumeImage& GetVolume() const + { + assert(GetOrigin().GetVolume()); + return *GetOrigin().GetVolume(); + } + }; + + + class VolumeUpdatedMessage : public OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + unsigned int axial_; + + public: + VolumeUpdatedMessage(const DicomVolumeLoader& loader, + unsigned int axial) : + OriginMessage(loader), + axial_(axial) + { + } + + unsigned int GetAxialIndex() const + { + return axial_; + } + + const DicomVolumeImage& GetVolume() const + { + assert(GetOrigin().GetVolume()); + return *GetOrigin().GetVolume(); + } + }; + + + class Factory : public ILoaderFactory + { + private: + SeriesFramesLoader::Factory framesFactory_; + bool computeRange_; + + public: + Factory(LoadedDicomResources& instances); + + Factory(const SeriesMetadataLoader::SeriesLoadedMessage& metadata); + + void SetComputeRange(bool computeRange) + { + computeRange_ = computeRange; + } + + void SetDicomDir(const std::string& dicomDirPath, + boost::shared_ptr dicomDir) + { + framesFactory_.SetDicomDir(dicomDirPath, dicomDir); + } + + virtual boost::shared_ptr Create(ILoadersContext::ILock& context) ORTHANC_OVERRIDE; + }; + + bool IsValid() const + { + return isValid_; + } + + bool IsFullyLoaded() const + { + return remaining_ == 0; + } + + boost::shared_ptr GetVolume() const + { + return volume_; + } + + const SeriesOrderedFrames& GetOrderedFrames() const + { + return framesLoader_->GetOrderedFrames(); + } + + void Start(int priority, + const DicomSource& source); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/GenericLoadersContext.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/GenericLoadersContext.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,183 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "GenericLoadersContext.h" + +namespace OrthancStone +{ + class GenericLoadersContext::Locker : public ILoadersContext::ILock + { + private: + GenericLoadersContext& that_; + boost::recursive_mutex::scoped_lock lock_; + + public: + Locker(GenericLoadersContext& that) : + that_(that), + lock_(that.mutex_) + { + if (!that_.scheduler_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + virtual ILoadersContext& GetContext() const ORTHANC_OVERRIDE + { + return that_; + }; + + virtual void AddLoader(boost::shared_ptr loader) ORTHANC_OVERRIDE + { + that_.loaders_.push_back(loader); + } + + virtual IObservable& GetOracleObservable() const ORTHANC_OVERRIDE + { + return that_.oracleObservable_; + } + + virtual void Schedule(boost::shared_ptr receiver, + int priority, + IOracleCommand* command /* Takes ownership */) ORTHANC_OVERRIDE + { + that_.scheduler_->Schedule(receiver, priority, command); + }; + + virtual void CancelRequests(boost::shared_ptr receiver) ORTHANC_OVERRIDE + { + that_.scheduler_->CancelRequests(receiver); + } + + virtual void CancelAllRequests() ORTHANC_OVERRIDE + { + that_.scheduler_->CancelAllRequests(); + } + + virtual void GetStatistics(uint64_t& scheduledCommands, + uint64_t& processedCommands) + { + scheduledCommands = that_.scheduler_->GetTotalScheduled(); + processedCommands = that_.scheduler_->GetTotalProcessed(); + } + }; + + + void GenericLoadersContext::EmitMessage(boost::weak_ptr observer, + const IMessage& message) + { + boost::recursive_mutex::scoped_lock lock(mutex_); + //LOG(INFO) << " inside emit lock: " << message.GetIdentifier().AsString(); + oracleObservable_.EmitMessage(observer, message); + //LOG(INFO) << " outside emit lock"; + } + + + GenericLoadersContext::GenericLoadersContext(unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority) + { + oracle_.reset(new ThreadedOracle(*this)); + scheduler_ = OracleScheduler::Create(*oracle_, oracleObservable_, *this, + maxHighPriority, maxStandardPriority, maxLowPriority); + + if (!scheduler_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + GenericLoadersContext::~GenericLoadersContext() + { + LOG(WARNING) << "scheduled commands: " << scheduler_->GetTotalScheduled() + << ", processed commands: " << scheduler_->GetTotalProcessed(); + scheduler_.reset(); + //LOG(INFO) << "counter: " << scheduler_.use_count(); + } + + + void GenericLoadersContext::SetOrthancParameters(const Orthanc::WebServiceParameters& parameters) + { + boost::recursive_mutex::scoped_lock lock(mutex_); + oracle_->SetOrthancParameters(parameters); + } + + + void GenericLoadersContext::SetRootDirectory(const std::string& root) + { + boost::recursive_mutex::scoped_lock lock(mutex_); + oracle_->SetRootDirectory(root); + } + + + void GenericLoadersContext::SetDicomCacheSize(size_t size) + { + boost::recursive_mutex::scoped_lock lock(mutex_); + oracle_->SetDicomCacheSize(size); + } + + + void GenericLoadersContext::StartOracle() + { + boost::recursive_mutex::scoped_lock lock(mutex_); + oracle_->Start(); + //LOG(INFO) << "STARTED ORACLE"; + } + + + void GenericLoadersContext::StopOracle() + { + /** + * DON'T lock "mutex_" here, otherwise Stone won't be able to + * stop if one command being executed by the oracle has to emit + * a message (method "EmitMessage()" would have to lock the + * mutex too). + **/ + + //LOG(INFO) << "STOPPING ORACLE"; + oracle_->Stop(); + //LOG(INFO) << "STOPPED ORACLE"; + } + + + void GenericLoadersContext::WaitUntilComplete() + { + for (;;) + { + { + boost::recursive_mutex::scoped_lock lock(mutex_); + if (scheduler_ && + scheduler_->GetTotalScheduled() == scheduler_->GetTotalProcessed()) + { + return; + } + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } + } + + ILoadersContext::ILock* GenericLoadersContext::Lock() + { + return new Locker(*this); + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/GenericLoadersContext.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/GenericLoadersContext.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Messages/IMessageEmitter.h" +#include "../Oracle/ThreadedOracle.h" +#include "ILoadersContext.h" +#include "DicomSource.h" +#include "OracleScheduler.h" + +#include + +namespace OrthancStone +{ + class GenericLoadersContext : + public ILoadersContext, + private IMessageEmitter + { + private: + class Locker; + + // "Recursive mutex" is necessary, to be able to run + // "ILoaderFactory" from a message handler triggered by + // "EmitMessage()" + boost::recursive_mutex mutex_; + + IObservable oracleObservable_; + std::unique_ptr oracle_; + boost::shared_ptr scheduler_; + + // Necessary to keep the loaders persistent (including global + // function promises), after the function that created them is + // left. This avoids creating one global variable for each loader. + std::list< boost::shared_ptr > loaders_; + + virtual void EmitMessage(boost::weak_ptr observer, + const IMessage& message); + + public: + GenericLoadersContext(unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority); + + virtual ~GenericLoadersContext(); + + virtual ILock* Lock() ORTHANC_OVERRIDE; + + void SetOrthancParameters(const Orthanc::WebServiceParameters& parameters); + + void SetRootDirectory(const std::string& root); + + void SetDicomCacheSize(size_t size); + + void StartOracle(); + + void StopOracle(); + + void WaitUntilComplete(); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/ILoaderFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/ILoaderFactory.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ILoadersContext.h" + +namespace OrthancStone +{ + class ILoaderFactory : public boost::noncopyable + { + public: + virtual ~ILoaderFactory() + { + } + + /** + * Factory function that creates a new loader, to be used by the + * Stone loaders context. + **/ + virtual boost::shared_ptr Create(ILoadersContext::ILock& context) = 0; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/ILoadersContext.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/ILoadersContext.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Messages/IObserver.h" +#include "../Messages/IObservable.h" +#include "../Oracle/IOracleCommand.h" + +#include + +namespace OrthancStone +{ + class ILoadersContext : public boost::noncopyable + { + public: + class ILock : public boost::noncopyable + { + public: + virtual ~ILock() + { + } + + /** + * This method is useful for loaders that must be able to + * re-lock the Stone loaders context in the future (for instance + * to schedule new commands once some command is processed). + **/ + virtual ILoadersContext& GetContext() const = 0; + + /** + * Get a reference to the observable against which a loader must + * listen to be informed of messages issued by the oracle once + * some command is processed. + **/ + virtual IObservable& GetOracleObservable() const = 0; + + /** + * Schedule a new command for further processing by the + * oracle. The "receiver" argument indicates to which object the + * notification messages are sent by the oracle upon completion + * of the command. The command is possibly not directly sent to + * the oracle: Instead, an internal "OracleScheduler" object is + * often used as a priority queue to rule the order in which + * commands are actually sent to the oracle. Hence the + * "priority" argument (commands with lower value are executed + * first). + **/ + virtual void Schedule(boost::shared_ptr receiver, + int priority, + IOracleCommand* command /* Takes ownership */) = 0; + + /** + * Cancel all the commands that are waiting in the + * "OracleScheduler" queue and that are linked to the given + * receiver (i.e. the observer that was specified at the time + * method "Schedule()" was called). This is useful for real-time + * processing, as it allows to replace commands that were + * scheduled in the past by more urgent commands. + * + * Note that this call does not affect commands that would have + * already be sent to the oracle. As a consequence, the receiver + * might still receive messages that were sent to the oracle + * before the cancellation (be prepared to handle such + * messages). + **/ + virtual void CancelRequests(boost::shared_ptr receiver) = 0; + + /** + * Same as "CancelRequests()", but targets all the receivers. + **/ + virtual void CancelAllRequests() = 0; + + /** + * Add a reference to the given observer in the Stone loaders + * context. This can be used to match the lifetime of a loader + * with the lifetime of the Stone context: This is useful if + * your Stone application does not keep a reference to the + * loader by itself (typically in global promises), which would + * make the loader disappear as soon as the scope of the + * variable is left. + **/ + virtual void AddLoader(boost::shared_ptr loader) = 0; + + /** + * Returns the number of commands that were scheduled and + * processed using the "Schedule()" method. By "processed" + * commands, we refer to the number of commands that were either + * executed by the oracle, or canceled by the user. So the + * counting sequences are monotonically increasing over time. + **/ + virtual void GetStatistics(uint64_t& scheduledCommands, + uint64_t& processedCommands) = 0; + }; + + virtual ~ILoadersContext() + { + } + + /** + * Locks the Stone loaders context, to give access to its + * underlying features. This is important for Stone applications + * running in a multi-threaded environment, for which a global + * mutex is locked. + **/ + virtual ILock* Lock() = 0; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/LoadedDicomResources.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/LoadedDicomResources.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,233 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "LoadedDicomResources.h" + +#include + +#include + + +namespace OrthancStone +{ + void LoadedDicomResources::Flatten() + { + // Lazy generation of a "std::vector" from the "std::map" + if (flattened_.empty()) + { + flattened_.resize(resources_.size()); + + size_t pos = 0; + for (Resources::const_iterator it = resources_.begin(); it != resources_.end(); ++it) + { + assert(it->second != NULL); + flattened_[pos++] = it->second; + } + } + else + { + // No need to flatten + assert(flattened_.size() == resources_.size()); + } + } + + + void LoadedDicomResources::AddFromDicomWebInternal(const Json::Value& dicomweb) + { + assert(dicomweb.type() == Json::objectValue); + Orthanc::DicomMap dicom; + dicom.FromDicomWeb(dicomweb); + AddResource(dicom); + } + + + LoadedDicomResources::LoadedDicomResources(const LoadedDicomResources& other, + const Orthanc::DicomTag& indexedTag) : + indexedTag_(indexedTag) + { + for (Resources::const_iterator it = other.resources_.begin(); + it != other.resources_.end(); ++it) + { + assert(it->second != NULL); + AddResource(*it->second); + } + } + + void LoadedDicomResources::Clear() + { + for (Resources::iterator it = resources_.begin(); it != resources_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + + resources_.clear(); + flattened_.clear(); + } + + + Orthanc::DicomMap& LoadedDicomResources::GetResource(size_t index) + { + Flatten(); + + if (index >= flattened_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + assert(flattened_[index] != NULL); + return *flattened_[index]; + } + } + + + void LoadedDicomResources::MergeResource(Orthanc::DicomMap& target, + const std::string& id) const + { + Resources::const_iterator it = resources_.find(id); + + if (it == resources_.end()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); + } + else + { + assert(it->second != NULL); + target.Merge(*it->second); + } + } + + + bool LoadedDicomResources::LookupStringValue(std::string& target, + const std::string& id, + const Orthanc::DicomTag& tag) const + { + Resources::const_iterator found = resources_.find(id); + + if (found == resources_.end()) + { + return false; + } + else + { + assert(found->second != NULL); + return found->second->LookupStringValue(target, tag, false); + } + } + + + void LoadedDicomResources::AddResource(const Orthanc::DicomMap& dicom) + { + std::string id; + + if (dicom.LookupStringValue(id, indexedTag_, false /* no binary value */) && + resources_.find(id) == resources_.end() /* Don't index twice the same resource */) + { + resources_[id] = dicom.Clone(); + flattened_.clear(); // Invalidate the flattened version + } + } + + + void LoadedDicomResources::AddFromOrthanc(const Json::Value& tags) + { + Orthanc::DicomMap dicom; + dicom.FromDicomAsJson(tags); + AddResource(dicom); + } + + + void LoadedDicomResources::AddFromDicomWeb(const Json::Value& dicomweb) + { + if (dicomweb.type() == Json::objectValue) + { + AddFromDicomWebInternal(dicomweb); + } + else if (dicomweb.type() == Json::arrayValue) + { + for (Json::Value::ArrayIndex i = 0; i < dicomweb.size(); i++) + { + if (dicomweb[i].type() == Json::objectValue) + { + AddFromDicomWebInternal(dicomweb[i]); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + + bool LoadedDicomResources::LookupTagValueConsensus(std::string& target, + const Orthanc::DicomTag& tag) const + { + typedef std::map Counter; + + Counter counter; + + for (Resources::const_iterator it = resources_.begin(); it != resources_.end(); ++it) + { + assert(it->second != NULL); + + std::string value; + if (it->second->LookupStringValue(value, tag, false)) + { + Counter::iterator found = counter.find(value); + if (found == counter.end()) + { + counter[value] = 1; + } + else + { + found->second ++; + } + } + } + + Counter::const_iterator best = counter.end(); + + for (Counter::const_iterator it = counter.begin(); it != counter.end(); ++it) + { + if (best == counter.end() || + best->second < it->second) + { + best = it; + } + } + + if (best == counter.end()) + { + return false; + } + else + { + target = best->first; + return true; + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/LoadedDicomResources.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/LoadedDicomResources.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,96 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include + + +namespace OrthancStone +{ + /** + Stores an indexed collection of DicomMap objects. The index is a + user-specified DicomTag. + */ + class LoadedDicomResources : public boost::noncopyable + { + private: + typedef std::map Resources; + + Orthanc::DicomTag indexedTag_; + Resources resources_; + std::vector flattened_; + + void Flatten(); + + void AddFromDicomWebInternal(const Json::Value& dicomweb); + + public: + LoadedDicomResources(const Orthanc::DicomTag& indexedTag) : + indexedTag_(indexedTag) + { + } + + // Re-index another set of resources using another tag + LoadedDicomResources(const LoadedDicomResources& other, + const Orthanc::DicomTag& indexedTag); + + ~LoadedDicomResources() + { + Clear(); + } + + const Orthanc::DicomTag& GetIndexedTag() const + { + return indexedTag_; + } + + void Clear(); + + size_t GetSize() const + { + return resources_.size(); + } + + Orthanc::DicomMap& GetResource(size_t index); + + bool HasResource(const std::string& id) const + { + return resources_.find(id) != resources_.end(); + } + + void MergeResource(Orthanc::DicomMap& target, + const std::string& id) const; + + bool LookupStringValue(std::string& target, + const std::string& id, + const Orthanc::DicomTag& tag) const; + + void AddResource(const Orthanc::DicomMap& dicom); + + void AddFromOrthanc(const Json::Value& tags); + + void AddFromDicomWeb(const Json::Value& dicomweb); + + bool LookupTagValueConsensus(std::string& target, + const Orthanc::DicomTag& tag) const; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/LoaderCache.cpp --- a/Framework/Loaders/LoaderCache.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Loaders/LoaderCache.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -25,11 +25,7 @@ #include "OrthancMultiframeVolumeLoader.h" #include "DicomStructureSetLoader.h" -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 -#include "DicomStructureSetLoader2.h" -#endif - //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - +#include "../Loaders/ILoadersContext.h" #if ORTHANC_ENABLE_WASM == 1 # include @@ -38,41 +34,21 @@ # include "../Oracle/ThreadedOracle.h" #endif -#include "../Messages/LockingEmitter.h" - -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 -#include "../Toolbox/DicomStructureSet2.h" -#endif -//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - #include "../Volumes/DicomVolumeImage.h" #include "../Volumes/DicomVolumeImageMPRSlicer.h" -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 -#include "../Volumes/DicomStructureSetSlicer2.h" -#endif -//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - #include #include namespace OrthancStone { -#if ORTHANC_ENABLE_WASM == 1 - LoaderCache::LoaderCache(WebAssemblyOracle& oracle) - : oracle_(oracle) + LoaderCache::LoaderCache(OrthancStone::ILoadersContext& loadersContext) + : loadersContext_(loadersContext) { } -#else - LoaderCache::LoaderCache(ThreadedOracle& oracle, LockingEmitter& lockingEmitter) - : oracle_(oracle) - , lockingEmitter_(lockingEmitter) - { - } -#endif - boost::shared_ptr + boost::shared_ptr LoaderCache::GetSeriesVolumeProgressiveLoader(std::string seriesUuid) { try @@ -85,26 +61,15 @@ // find in cache if (seriesVolumeProgressiveLoaders_.find(seriesUuid) == seriesVolumeProgressiveLoaders_.end()) { -// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : CACHEMISS --> need to load seriesUUid = " << seriesUuid; -#if ORTHANC_ENABLE_WASM == 1 -// LOG(TRACE) << "Performing request for series " << seriesUuid << " sbrk(0) = " << sbrk(0); -#else -// LOG(TRACE) << "Performing request for series " << seriesUuid; -#endif - boost::shared_ptr volumeImage(new DicomVolumeImage); + std::unique_ptr lock(loadersContext_.Lock()); + + boost::shared_ptr volumeImage(new OrthancStone::DicomVolumeImage); boost::shared_ptr loader; -// LOG(TRACE) << "volumeImage = " << volumeImage.get(); - { -#if ORTHANC_ENABLE_WASM == 1 - loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, oracle_)); -#else - LockingEmitter::WriterLock lock(lockingEmitter_); - loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, lock.GetOracleObservable())); -#endif -// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader = " << loader.get(); - loader->LoadSeries(seriesUuid); -// LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader->LoadSeries successful"; - } + + // true means "use progressive quality" + // false means "load high quality slices only" + loader = OrthancSeriesVolumeProgressiveLoader::Create(loadersContext_, volumeImage, false); + loader->LoadSeries(seriesUuid); seriesVolumeProgressiveLoaders_[seriesUuid] = loader; } else @@ -149,7 +114,7 @@ return multiframeVolumeLoaders_[instanceUuid]; } - boost::shared_ptr LoaderCache::GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid) + boost::shared_ptr LoaderCache::GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid) { try { @@ -160,22 +125,15 @@ // find in cache if (dicomVolumeImageMPRSlicers_.find(instanceUuid) == dicomVolumeImageMPRSlicers_.end()) { - boost::shared_ptr volumeImage(new DicomVolumeImage); + std::unique_ptr lock(loadersContext_.Lock()); + boost::shared_ptr volumeImage(new OrthancStone::DicomVolumeImage); boost::shared_ptr loader; - { -#if ORTHANC_ENABLE_WASM == 1 - loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, oracle_)); -#else - LockingEmitter::WriterLock lock(lockingEmitter_); - loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, - oracle_, - lock.GetOracleObservable())); -#endif + loader = OrthancMultiframeVolumeLoader::Create(loadersContext_, volumeImage); loader->LoadInstance(instanceUuid); } multiframeVolumeLoaders_[instanceUuid] = loader; - boost::shared_ptr mprSlicer(new DicomVolumeImageMPRSlicer(volumeImage)); + boost::shared_ptr mprSlicer(new OrthancStone::DicomVolumeImageMPRSlicer(volumeImage)); dicomVolumeImageMPRSlicers_[instanceUuid] = mprSlicer; } return dicomVolumeImageMPRSlicers_[instanceUuid]; @@ -204,23 +162,6 @@ } } -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - boost::shared_ptr LoaderCache::GetDicomStructureSetSlicer2(std::string instanceUuid) - { - // if the loader is not available, let's trigger its creation - if (dicomStructureSetSlicers2_.find(instanceUuid) == dicomStructureSetSlicers2_.end()) - { - GetDicomStructureSetLoader2(instanceUuid); - } - ORTHANC_ASSERT(dicomStructureSetSlicers2_.find(instanceUuid) != dicomStructureSetSlicers2_.end()); - - return dicomStructureSetSlicers2_[instanceUuid]; - } -#endif -//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - /** This method allows to convert a list of string into a string by sorting the strings then joining them @@ -264,15 +205,11 @@ // find in cache if (dicomStructureSetLoaders_.find(entryKey) == dicomStructureSetLoaders_.end()) { + std::unique_ptr lock(loadersContext_.Lock()); + boost::shared_ptr loader; - { -#if ORTHANC_ENABLE_WASM == 1 - loader.reset(new DicomStructureSetLoader(oracle_, oracle_)); -#else - LockingEmitter::WriterLock lock(lockingEmitter_); - loader.reset(new DicomStructureSetLoader(oracle_, lock.GetOracleObservable())); -#endif + loader = DicomStructureSetLoader::Create(loadersContext_); loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures); } dicomStructureSetLoaders_[entryKey] = loader; @@ -303,88 +240,19 @@ } } -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - boost::shared_ptr LoaderCache::GetDicomStructureSetLoader2(std::string instanceUuid) - { - try - { - // normalize keys a little - instanceUuid = Orthanc::Toolbox::StripSpaces(instanceUuid); - Orthanc::Toolbox::ToLowerCase(instanceUuid); - - // find in cache - if (dicomStructureSetLoaders2_.find(instanceUuid) == dicomStructureSetLoaders2_.end()) - { - boost::shared_ptr loader; - boost::shared_ptr structureSet(new DicomStructureSet2()); - boost::shared_ptr rtSlicer(new DicomStructureSetSlicer2(structureSet)); - dicomStructureSetSlicers2_[instanceUuid] = rtSlicer; - dicomStructureSets2_[instanceUuid] = structureSet; // to prevent it from being deleted - { -#if ORTHANC_ENABLE_WASM == 1 - loader.reset(new DicomStructureSetLoader2(*(structureSet.get()), oracle_, oracle_)); -#else - LockingEmitter::WriterLock lock(lockingEmitter_); - // TODO: clarify lifetimes... this is DANGEROUS! - loader.reset(new DicomStructureSetLoader2(*(structureSet.get()), oracle_, lock.GetOracleObservable())); -#endif - loader->LoadInstance(instanceUuid); - } - dicomStructureSetLoaders2_[instanceUuid] = loader; - } - return dicomStructureSetLoaders2_[instanceUuid]; - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in GetDicomStructureSetLoader2: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in GetDicomStructureSetLoader2: " << e.What(); - } - throw; - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in GetDicomStructureSetLoader2: " << e.what(); - throw; - } - catch (...) - { - LOG(ERROR) << "Unknown exception in GetDicomStructureSetLoader2"; - throw; - } - } - -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - - void LoaderCache::ClearCache() { -#if ORTHANC_ENABLE_WASM != 1 - LockingEmitter::WriterLock lock(lockingEmitter_); -#endif + std::unique_ptr lock(loadersContext_.Lock()); -//#ifndef NDEBUG +#ifndef NDEBUG // ISO way of checking for debug builds DebugDisplayObjRefCounts(); -//#endif +#endif seriesVolumeProgressiveLoaders_.clear(); multiframeVolumeLoaders_.clear(); dicomVolumeImageMPRSlicers_.clear(); dicomStructureSetLoaders_.clear(); -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - // order is important! - dicomStructureSetLoaders2_.clear(); - dicomStructureSetSlicers2_.clear(); - dicomStructureSets2_.clear(); -#endif -// BGO_ENABLE_DICOMSTRUCTURESETLOADER2 } template void DebugDisplayObjRefCountsInMap( @@ -406,10 +274,5 @@ DebugDisplayObjRefCountsInMap("multiframeVolumeLoaders_", multiframeVolumeLoaders_); DebugDisplayObjRefCountsInMap("dicomVolumeImageMPRSlicers_", dicomVolumeImageMPRSlicers_); DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders_", dicomStructureSetLoaders_); -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - DebugDisplayObjRefCountsInMap("dicomStructureSetLoaders2_", dicomStructureSetLoaders2_); - DebugDisplayObjRefCountsInMap("dicomStructureSetSlicers2_", dicomStructureSetSlicers2_); -#endif -//BGO_ENABLE_DICOMSTRUCTURESETLOADER2 } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/LoaderCache.h --- a/Framework/Loaders/LoaderCache.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Loaders/LoaderCache.h Wed Apr 22 14:05:47 2020 +0200 @@ -20,6 +20,11 @@ #pragma once +#include "../Volumes/DicomVolumeImageMPRSlicer.h" +#include "OrthancSeriesVolumeProgressiveLoader.h" +#include "OrthancMultiframeVolumeLoader.h" +#include "DicomStructureSetLoader.h" + #include #include @@ -28,37 +33,20 @@ namespace OrthancStone { - class OrthancSeriesVolumeProgressiveLoader; - class DicomVolumeImageMPRSlicer; - class DicomStructureSetLoader; -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - class DicomStructureSetLoader2; - class DicomStructureSetSlicer2; - class DicomStructureSet2; -#endif - //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - class OrthancMultiframeVolumeLoader; + class ILoadersContext; +} -#if ORTHANC_ENABLE_WASM == 1 - class WebAssemblyOracle; -#else - class ThreadedOracle; - class LockingEmitter; -#endif - +namespace OrthancStone +{ class LoaderCache { public: -#if ORTHANC_ENABLE_WASM == 1 - LoaderCache(WebAssemblyOracle& oracle); -#else - LoaderCache(ThreadedOracle& oracle, LockingEmitter& lockingEmitter); -#endif + LoaderCache(OrthancStone::ILoadersContext& loadersContext); boost::shared_ptr GetSeriesVolumeProgressiveLoader (std::string seriesUuid); - boost::shared_ptr + boost::shared_ptr GetMultiframeDicomVolumeImageMPRSlicer(std::string instanceUuid); boost::shared_ptr @@ -69,44 +57,22 @@ std::string instanceUuid, const std::vector& initiallyVisibleStructures); -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - boost::shared_ptr - GetDicomStructureSetLoader2(std::string instanceUuid); - - boost::shared_ptr - GetDicomStructureSetSlicer2(std::string instanceUuid); -#endif - //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - void ClearCache(); private: void DebugDisplayObjRefCounts(); -#if ORTHANC_ENABLE_WASM == 1 - WebAssemblyOracle& oracle_; -#else - ThreadedOracle& oracle_; - LockingEmitter& lockingEmitter_; -#endif + + OrthancStone::ILoadersContext& loadersContext_; std::map > seriesVolumeProgressiveLoaders_; std::map > multiframeVolumeLoaders_; - std::map > + std::map > dicomVolumeImageMPRSlicers_; std::map > dicomStructureSetLoaders_; -#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2 - std::map > - dicomStructureSetLoaders2_; - std::map > - dicomStructureSets2_; - std::map > - dicomStructureSetSlicers2_; -#endif - //BGO_ENABLE_DICOMSTRUCTURESETLOADER2 }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/LoaderStateMachine.cpp --- a/Framework/Loaders/LoaderStateMachine.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Loaders/LoaderStateMachine.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,33 +21,35 @@ #include "LoaderStateMachine.h" +#include "../Loaders/ILoadersContext.h" + #include namespace OrthancStone { - void LoaderStateMachine::State::Handle(const OrthancRestApiCommand::SuccessMessage& message) + void LoaderStateMachine::State::Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - void LoaderStateMachine::State::Handle(const GetOrthancImageCommand::SuccessMessage& message) + void LoaderStateMachine::State::Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - void LoaderStateMachine::State::Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) + void LoaderStateMachine::State::Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - void LoaderStateMachine::Schedule(OracleCommandWithPayload* command) + void LoaderStateMachine::Schedule(OrthancStone::OracleCommandBase* command) { LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Schedule()"; - std::unique_ptr protection(command); + std::unique_ptr protection(command); if (command == NULL) { @@ -90,14 +92,18 @@ activeCommands_ < simultaneousDownloads_) { - IOracleCommand* nextCommand = pendingCommands_.front(); + OrthancStone::IOracleCommand* nextCommand = pendingCommands_.front(); LOG(TRACE) << " LoaderStateMachine(" << std::hex << this << std::dec << ")::Step(): activeCommands_ (" << activeCommands_ << ") < simultaneousDownloads_ (" << simultaneousDownloads_ << ") --> will Schedule command addr " << std::hex << nextCommand << std::dec; - oracle_.Schedule(*this, nextCommand); + { + std::unique_ptr lock(loadersContext_.Lock()); + boost::shared_ptr observer(GetSharedObserver()); + lock->Schedule(observer, 0, nextCommand); // TODO: priority! + } pendingCommands_.pop_front(); activeCommands_++; @@ -123,9 +129,9 @@ pendingCommands_.clear(); } - + - void LoaderStateMachine::HandleExceptionMessage(const OracleCommandExceptionMessage& message) + void LoaderStateMachine::HandleExceptionMessage(const OrthancStone::OracleCommandExceptionMessage& message) { LOG(ERROR) << "LoaderStateMachine::HandleExceptionMessage: error in the state machine, stopping all processing"; LOG(ERROR) << "Error: " << message.GetException().What() << " Details: " << @@ -136,7 +142,6 @@ template void LoaderStateMachine::HandleSuccessMessage(const T& message) { - LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::HandleSuccessMessage(). Receiver fingerprint = " << GetFingerprint(); if (activeCommands_ <= 0) { LOG(ERROR) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::HandleSuccessMessage : activeCommands_ should be > 0 but is: " << activeCommands_; } @@ -157,37 +162,40 @@ } - LoaderStateMachine::LoaderStateMachine(IOracle& oracle, - IObservable& oracleObservable) : - IObserver(oracleObservable.GetBroker()), - oracle_(oracle), - oracleObservable_(oracleObservable), - active_(false), - simultaneousDownloads_(4), - activeCommands_(0) + LoaderStateMachine::LoaderStateMachine( + OrthancStone::ILoadersContext& loadersContext) + : loadersContext_(loadersContext) + , active_(false) + , simultaneousDownloads_(4) + , activeCommands_(0) { - LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::LoaderStateMachine()"; + using OrthancStone::ILoadersContext; + + LOG(TRACE) + << "LoaderStateMachine(" << std::hex << this + << std::dec << ")::LoaderStateMachine()"; + } - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &LoaderStateMachine::HandleSuccessMessage)); + void LoaderStateMachine::PostConstructor() + { + std::unique_ptr + lock(loadersContext_.Lock()); - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &LoaderStateMachine::HandleSuccessMessage)); + OrthancStone::IObservable& observable = lock->GetOracleObservable(); - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &LoaderStateMachine::HandleSuccessMessage)); - - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &LoaderStateMachine::HandleExceptionMessage)); + // TODO => Move this out of constructor + Register( + observable, &LoaderStateMachine::HandleSuccessMessage); + Register( + observable, &LoaderStateMachine::HandleSuccessMessage); + Register( + observable, &LoaderStateMachine::HandleSuccessMessage); + Register( + observable, &LoaderStateMachine::HandleExceptionMessage); } LoaderStateMachine::~LoaderStateMachine() { - oracleObservable_.Unregister(this); LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::~LoaderStateMachine()"; Clear(); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/LoaderStateMachine.h --- a/Framework/Loaders/LoaderStateMachine.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Loaders/LoaderStateMachine.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Messages/IObservable.h" -#include "../Messages/IObserver.h" +#include "../Messages/ObserverBase.h" #include "../Oracle/GetOrthancImageCommand.h" #include "../Oracle/GetOrthancWebViewerJpegCommand.h" #include "../Oracle/IOracle.h" @@ -35,15 +35,23 @@ namespace OrthancStone { + class ILoadersContext; + /** 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. + + To use it, you need to create commands that derive from State. + + You need to initialize them with the object that must be called when + an answer is received. */ - class LoaderStateMachine : public IObserver + + class LoaderStateMachine : public OrthancStone::ObserverBase { - protected: + public: class State : public Orthanc::IDynamicObject { private: @@ -60,7 +68,7 @@ { } - void Schedule(OracleCommandWithPayload* command) const + void Schedule(OrthancStone::OracleCommandBase* command) const { that_.Schedule(command); } @@ -71,14 +79,14 @@ return dynamic_cast(that_); } - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message); + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message); - virtual void Handle(const GetOrthancImageCommand::SuccessMessage& message); + virtual void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message); - virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message); + virtual void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message); }; - void Schedule(OracleCommandWithPayload* command); + void Schedule(OrthancStone::OracleCommandBase* command); void Start(); @@ -87,23 +95,24 @@ void Clear(); - void HandleExceptionMessage(const OracleCommandExceptionMessage& message); + void HandleExceptionMessage(const OrthancStone::OracleCommandExceptionMessage& message); template void HandleSuccessMessage(const T& message); - typedef std::list PendingCommands; + typedef std::list PendingCommands; - IOracle& oracle_; - IObservable& oracleObservable_; - bool active_; - unsigned int simultaneousDownloads_; - PendingCommands pendingCommands_; - unsigned int activeCommands_; + OrthancStone::ILoadersContext& loadersContext_; + bool active_; + unsigned int simultaneousDownloads_; + PendingCommands pendingCommands_; + unsigned int activeCommands_; + public: - LoaderStateMachine(IOracle& oracle, - IObservable& oracleObservable); + LoaderStateMachine(OrthancStone::ILoadersContext& loadersContext); + + void PostConstructor(); virtual ~LoaderStateMachine(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/OracleScheduler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/OracleScheduler.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,557 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OracleScheduler.h" + +#include "../Oracle/ParseDicomFromFileCommand.h" + +namespace OrthancStone +{ + class OracleScheduler::ReceiverPayload : public Orthanc::IDynamicObject + { + private: + Priority priority_; + boost::weak_ptr receiver_; + std::unique_ptr command_; + + public: + ReceiverPayload(Priority priority, + boost::weak_ptr receiver, + IOracleCommand* command) : + priority_(priority), + receiver_(receiver), + command_(command) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + Priority GetActivePriority() const + { + return priority_; + } + + boost::weak_ptr GetOriginalReceiver() const + { + return receiver_; + } + + const IOracleCommand& GetOriginalCommand() const + { + assert(command_.get() != NULL); + return *command_; + } + }; + + + class OracleScheduler::ScheduledCommand : public boost::noncopyable + { + private: + boost::weak_ptr receiver_; + std::unique_ptr command_; + + public: + ScheduledCommand(boost::shared_ptr receiver, + IOracleCommand* command) : + receiver_(receiver), + command_(command) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + boost::weak_ptr GetReceiver() + { + return receiver_; + } + + bool IsSameReceiver(boost::shared_ptr receiver) const + { + boost::shared_ptr lock(receiver_.lock()); + + return (lock && + lock.get() == receiver.get()); + } + + IOracleCommand* WrapCommand(Priority priority) + { + if (command_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + std::unique_ptr wrapped(command_->Clone()); + dynamic_cast(*wrapped).AcquirePayload(new ReceiverPayload(priority, receiver_, command_.release())); + return wrapped.release(); + } + } + }; + + + + void OracleScheduler::ClearQueue(Queue& queue) + { + for (Queue::iterator it = queue.begin(); it != queue.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + + totalProcessed_ ++; + } + + queue.clear(); + } + + + void OracleScheduler::RemoveReceiverFromQueue(Queue& queue, + boost::shared_ptr receiver) + { + if (!receiver) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + Queue tmp; + + for (Queue::iterator it = queue.begin(); it != queue.end(); ++it) + { + assert(it->second != NULL); + + if (!(it->second->IsSameReceiver(receiver))) + { + // This promise is still active + tmp.insert(std::make_pair(it->first, it->second)); + } + else + { + delete it->second; + + totalProcessed_ ++; + } + } + + queue = tmp; + } + + + void OracleScheduler::CheckInvariants() const + { +#ifndef NDEBUG + /*char buf[1024]; + sprintf(buf, "active: %d %d %d ; pending: %lu %lu %lu", + activeHighPriorityCommands_, activeStandardPriorityCommands_, activeLowPriorityCommands_, + highPriorityQueue_.size(), standardPriorityQueue_.size(), lowPriorityQueue_.size()); + LOG(INFO) << buf;*/ + + assert(activeHighPriorityCommands_ <= maxHighPriorityCommands_); + assert(activeStandardPriorityCommands_ <= maxStandardPriorityCommands_); + assert(activeLowPriorityCommands_ <= maxLowPriorityCommands_); + assert(totalProcessed_ <= totalScheduled_); + + for (Queue::const_iterator it = standardPriorityQueue_.begin(); it != standardPriorityQueue_.end(); ++it) + { + assert(it->first > PRIORITY_HIGH && + it->first < PRIORITY_LOW); + } + + for (Queue::const_iterator it = highPriorityQueue_.begin(); it != highPriorityQueue_.end(); ++it) + { + assert(it->first <= PRIORITY_HIGH); + } + + for (Queue::const_iterator it = lowPriorityQueue_.begin(); it != lowPriorityQueue_.end(); ++it) + { + assert(it->first >= PRIORITY_LOW); + } +#endif + } + + + void OracleScheduler::SpawnFromQueue(Queue& queue, + Priority priority) + { + CheckInvariants(); + + Queue::iterator item = queue.begin(); + assert(item != queue.end()); + + std::unique_ptr command(dynamic_cast(item->second)); + queue.erase(item); + + if (command.get() != NULL) + { + /** + * Only schedule the command for execution in the oracle, if its + * receiver has not been destroyed yet. + **/ + boost::shared_ptr observer(command->GetReceiver().lock()); + if (observer) + { + if (oracle_.Schedule(GetSharedObserver(), command->WrapCommand(priority))) + { + /** + * Executing this code if "Schedule()" returned "false" + * above, will result in a memory leak within + * "OracleScheduler", as the scheduler believes that some + * command is still active (i.e. pending to be executed by + * the oracle), hereby stalling the scheduler during its + * destruction, and not freeing the + * "shared_ptr" of the Stone context (check + * out "sjo-playground/WebViewer/Backend/Leak") + **/ + + switch (priority) + { + case Priority_High: + activeHighPriorityCommands_ ++; + break; + + case Priority_Standard: + activeStandardPriorityCommands_ ++; + break; + + case Priority_Low: + activeLowPriorityCommands_ ++; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + else + { + totalProcessed_ ++; + } + } + } + else + { + LOG(ERROR) << "NULL command, should never happen"; + } + + CheckInvariants(); + } + + + void OracleScheduler::SpawnCommands() + { + // Send as many commands as possible to the oracle + while (!highPriorityQueue_.empty()) + { + if (activeHighPriorityCommands_ < maxHighPriorityCommands_) + { + // First fill the high-priority lane + SpawnFromQueue(highPriorityQueue_, Priority_High); + } + else if (activeStandardPriorityCommands_ < maxStandardPriorityCommands_) + { + // There remain too many high-priority commands for the + // high-priority lane, schedule them to the standard-priority lanes + SpawnFromQueue(highPriorityQueue_, Priority_Standard); + } + else if (activeLowPriorityCommands_ < maxLowPriorityCommands_) + { + SpawnFromQueue(highPriorityQueue_, Priority_Low); + } + else + { + return; // No slot available + } + } + + while (!standardPriorityQueue_.empty()) + { + if (activeStandardPriorityCommands_ < maxStandardPriorityCommands_) + { + SpawnFromQueue(standardPriorityQueue_, Priority_Standard); + } + else if (activeLowPriorityCommands_ < maxLowPriorityCommands_) + { + SpawnFromQueue(standardPriorityQueue_, Priority_Low); + } + else + { + return; + } + } + + while (!lowPriorityQueue_.empty()) + { + if (activeLowPriorityCommands_ < maxLowPriorityCommands_) + { + SpawnFromQueue(lowPriorityQueue_, Priority_Low); + } + else + { + return; + } + } + } + + + void OracleScheduler::RemoveActiveCommand(const ReceiverPayload& payload) + { + CheckInvariants(); + + totalProcessed_ ++; + + switch (payload.GetActivePriority()) + { + case Priority_High: + assert(activeHighPriorityCommands_ > 0); + activeHighPriorityCommands_ --; + break; + + case Priority_Standard: + assert(activeStandardPriorityCommands_ > 0); + activeStandardPriorityCommands_ --; + break; + + case Priority_Low: + assert(activeLowPriorityCommands_ > 0); + activeLowPriorityCommands_ --; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + SpawnCommands(); + + CheckInvariants(); + } + + + void OracleScheduler::Handle(const GetOrthancImageCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + const ReceiverPayload& payload = dynamic_cast(message.GetOrigin().GetPayload()); + + RemoveActiveCommand(payload); + + GetOrthancImageCommand::SuccessMessage bis( + dynamic_cast(payload.GetOriginalCommand()), + message.GetImage(), message.GetMimeType()); + emitter_.EmitMessage(payload.GetOriginalReceiver(), bis); + } + + + void OracleScheduler::Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + const ReceiverPayload& payload = dynamic_cast(message.GetOrigin().GetPayload()); + + RemoveActiveCommand(payload); + + GetOrthancWebViewerJpegCommand::SuccessMessage bis( + dynamic_cast(payload.GetOriginalCommand()), + message.GetImage()); + emitter_.EmitMessage(payload.GetOriginalReceiver(), bis); + } + + + void OracleScheduler::Handle(const HttpCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + const ReceiverPayload& payload = dynamic_cast(message.GetOrigin().GetPayload()); + + RemoveActiveCommand(payload); + + HttpCommand::SuccessMessage bis( + dynamic_cast(payload.GetOriginalCommand()), + message.GetAnswerHeaders(), message.GetAnswer()); + emitter_.EmitMessage(payload.GetOriginalReceiver(), bis); + } + + + void OracleScheduler::Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + const ReceiverPayload& payload = dynamic_cast(message.GetOrigin().GetPayload()); + + RemoveActiveCommand(payload); + + OrthancRestApiCommand::SuccessMessage bis( + dynamic_cast(payload.GetOriginalCommand()), + message.GetAnswerHeaders(), message.GetAnswer()); + emitter_.EmitMessage(payload.GetOriginalReceiver(), bis); + } + + +#if ORTHANC_ENABLE_DCMTK == 1 + void OracleScheduler::Handle(const ParseDicomSuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + const ReceiverPayload& payload = dynamic_cast(message.GetOrigin().GetPayload()); + + RemoveActiveCommand(payload); + + ParseDicomSuccessMessage bis( + dynamic_cast(payload.GetOriginalCommand()), + message.GetDicom(), message.GetFileSize(), message.HasPixelData()); + emitter_.EmitMessage(payload.GetOriginalReceiver(), bis); + } +#endif + + + void OracleScheduler::Handle(const ReadFileCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + const ReceiverPayload& payload = dynamic_cast(message.GetOrigin().GetPayload()); + + RemoveActiveCommand(payload); + + ReadFileCommand::SuccessMessage bis( + dynamic_cast(payload.GetOriginalCommand()), + message.GetContent()); + emitter_.EmitMessage(payload.GetOriginalReceiver(), bis); + } + + + void OracleScheduler::Handle(const OracleCommandExceptionMessage& message) + { + const OracleCommandBase& command = dynamic_cast(message.GetOrigin()); + + assert(command.HasPayload()); + const ReceiverPayload& payload = dynamic_cast(command.GetPayload()); + + RemoveActiveCommand(payload); + + OracleCommandExceptionMessage bis(payload.GetOriginalCommand(), message.GetException()); + emitter_.EmitMessage(payload.GetOriginalReceiver(), bis); + } + + + OracleScheduler::OracleScheduler(IOracle& oracle, + IMessageEmitter& emitter, + unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority) : + oracle_(oracle), + emitter_(emitter), + maxHighPriorityCommands_(maxHighPriority), + maxStandardPriorityCommands_(maxStandardPriority), + maxLowPriorityCommands_(maxLowPriority), + activeHighPriorityCommands_(0), + activeStandardPriorityCommands_(0), + activeLowPriorityCommands_(0), + totalScheduled_(0), + totalProcessed_(0) + { + assert(PRIORITY_HIGH < 0 && + PRIORITY_LOW > 0); + + if (maxLowPriority <= 0) + { + // There must be at least 1 lane available to deal with low-priority commands + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + boost::shared_ptr OracleScheduler::Create(IOracle& oracle, + IObservable& oracleObservable, + IMessageEmitter& emitter, + unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority) + { + boost::shared_ptr scheduler + (new OracleScheduler(oracle, emitter, maxHighPriority, maxStandardPriority, maxLowPriority)); + scheduler->Register(oracleObservable, &OracleScheduler::Handle); + scheduler->Register(oracleObservable, &OracleScheduler::Handle); + scheduler->Register(oracleObservable, &OracleScheduler::Handle); + scheduler->Register(oracleObservable, &OracleScheduler::Handle); + scheduler->Register(oracleObservable, &OracleScheduler::Handle); + scheduler->Register(oracleObservable, &OracleScheduler::Handle); + +#if ORTHANC_ENABLE_DCMTK == 1 + scheduler->Register(oracleObservable, &OracleScheduler::Handle); +#endif + + return scheduler; + } + + + OracleScheduler::~OracleScheduler() + { + CancelAllRequests(); + } + + + void OracleScheduler::CancelRequests(boost::shared_ptr receiver) + { + RemoveReceiverFromQueue(standardPriorityQueue_, receiver); + RemoveReceiverFromQueue(highPriorityQueue_, receiver); + RemoveReceiverFromQueue(lowPriorityQueue_, receiver); + } + + + void OracleScheduler::CancelAllRequests() + { + ClearQueue(standardPriorityQueue_); + ClearQueue(highPriorityQueue_); + ClearQueue(lowPriorityQueue_); + } + + + void OracleScheduler::Schedule(boost::shared_ptr receiver, + int priority, + IOracleCommand* command /* Takes ownership */) + { + std::unique_ptr pending(new ScheduledCommand(receiver, dynamic_cast(command))); + + /** + * Safeguard to remember that a new "Handle()" method and a call + * to "scheduler->Register()" must be implemented for each + * possible oracle command. + **/ + assert(command->GetType() == IOracleCommand::Type_GetOrthancImage || + command->GetType() == IOracleCommand::Type_GetOrthancWebViewerJpeg || + command->GetType() == IOracleCommand::Type_Http || + command->GetType() == IOracleCommand::Type_OrthancRestApi || + command->GetType() == IOracleCommand::Type_ParseDicomFromFile || + command->GetType() == IOracleCommand::Type_ParseDicomFromWado || + command->GetType() == IOracleCommand::Type_ReadFile); + + if (priority <= PRIORITY_HIGH) + { + highPriorityQueue_.insert(std::make_pair(priority, pending.release())); + } + else if (priority >= PRIORITY_LOW) + { + lowPriorityQueue_.insert(std::make_pair(priority, pending.release())); + } + else + { + standardPriorityQueue_.insert(std::make_pair(priority, pending.release())); + } + + totalScheduled_ ++; + + SpawnCommands(); + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/OracleScheduler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/OracleScheduler.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,167 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + +#include "../Messages/IMessageEmitter.h" +#include "../Messages/ObserverBase.h" +#include "../Oracle/GetOrthancImageCommand.h" +#include "../Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../Oracle/HttpCommand.h" +#include "../Oracle/IOracle.h" +#include "../Oracle/OracleCommandExceptionMessage.h" +#include "../Oracle/OrthancRestApiCommand.h" +#include "../Oracle/ReadFileCommand.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "../Oracle/ParseDicomSuccessMessage.h" +#endif + +namespace OrthancStone +{ + class OracleScheduler : public ObserverBase + { + public: + static const int PRIORITY_HIGH = -1; + static const int PRIORITY_LOW = 100; + + private: + enum Priority + { + Priority_Low, + Priority_Standard, + Priority_High + }; + + class ReceiverPayload; + class ScheduledCommand; + + typedef std::multimap Queue; + + IOracle& oracle_; + IMessageEmitter& emitter_; + Queue standardPriorityQueue_; + Queue highPriorityQueue_; + Queue lowPriorityQueue_; + unsigned int maxHighPriorityCommands_; // Used if priority <= PRIORITY_HIGH + unsigned int maxStandardPriorityCommands_; + unsigned int maxLowPriorityCommands_; // Used if priority >= PRIORITY_LOW + unsigned int activeHighPriorityCommands_; + unsigned int activeStandardPriorityCommands_; + unsigned int activeLowPriorityCommands_; + uint64_t totalScheduled_; + uint64_t totalProcessed_; + + void ClearQueue(Queue& queue); + + void RemoveReceiverFromQueue(Queue& queue, + boost::shared_ptr receiver); + + void CheckInvariants() const; + + void SpawnFromQueue(Queue& queue, + Priority priority); + + void SpawnCommands(); + + void RemoveActiveCommand(const ReceiverPayload& payload); + + void Handle(const GetOrthancImageCommand::SuccessMessage& message); + + void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message); + + void Handle(const HttpCommand::SuccessMessage& message); + + void Handle(const OrthancRestApiCommand::SuccessMessage& message); + +#if ORTHANC_ENABLE_DCMTK == 1 + void Handle(const ParseDicomSuccessMessage& message); +#endif + + void Handle(const ReadFileCommand::SuccessMessage& message); + + void Handle(const OracleCommandExceptionMessage& message); + + OracleScheduler(IOracle& oracle, + IMessageEmitter& emitter, + unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority); + + public: + static boost::shared_ptr Create(IOracle& oracle, + IObservable& oracleObservable, + IMessageEmitter& emitter) + { + return Create(oracle, oracleObservable, emitter, 1, 4, 1); + } + + static boost::shared_ptr Create(IOracle& oracle, + IObservable& oracleObservable, + IMessageEmitter& emitter, + unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority); + + ~OracleScheduler(); + + unsigned int GetMaxHighPriorityCommands() const + { + return maxHighPriorityCommands_; + } + + unsigned int GetMaxStandardPriorityCommands() const + { + return maxStandardPriorityCommands_; + } + + unsigned int GetMaxLowPriorityCommands() const + { + return maxLowPriorityCommands_; + } + + uint64_t GetTotalScheduled() const + { + return totalScheduled_; + } + + uint64_t GetTotalProcessed() const + { + return totalProcessed_; + } + + // Cancel the HTTP requests that are still pending in the queues, + // and that are associated with the given receiver. Note that the + // receiver might still receive answers to HTTP requests that were + // already submitted to the oracle. + void CancelRequests(boost::shared_ptr receiver); + + void CancelAllRequests(); + + void Schedule(boost::shared_ptr receiver, + int priority, + IOracleCommand* command /* Takes ownership */); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/OrthancMultiframeVolumeLoader.cpp --- a/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -44,7 +44,7 @@ } - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { // Complete the DICOM tags with just-received "Grid Frame Offset Vector" std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer()); @@ -78,7 +78,7 @@ { } - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { OrthancMultiframeVolumeLoader& loader = GetLoader(); @@ -93,15 +93,15 @@ std::unique_ptr dicom(new Orthanc::DicomMap); dicom->FromDicomAsJson(body); - if (StringToSopClassUid(GetSopClassUid(*dicom)) == SopClassUid_RTDose) + if (OrthancStone::StringToSopClassUid(GetSopClassUid(*dicom)) == OrthancStone::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::unique_ptr command(new OrthancRestApiCommand); + std::unique_ptr command(new OrthancStone::OrthancRestApiCommand); command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" + Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format()); - command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release())); + command->AcquirePayload(new LoadRTDoseGeometry(loader, dicom.release())); Schedule(command.release()); } @@ -120,7 +120,7 @@ { } - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { GetLoader().SetTransferSyntax(message.GetAnswer()); } @@ -134,7 +134,7 @@ { } - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) + virtual void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { GetLoader().SetUncompressedPixelData(message.GetAnswer()); } @@ -171,11 +171,11 @@ transferSyntaxUid_ == "1.2.840.10008.1.2.1" || transferSyntaxUid_ == "1.2.840.10008.1.2.2") { - std::unique_ptr command(new OrthancRestApiCommand); + std::unique_ptr command(new OrthancStone::OrthancRestApiCommand); command->SetHttpHeader("Accept-Encoding", "gzip"); command->SetUri("/instances/" + instanceId_ + "/content/" + Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0"); - command->SetPayload(new LoadUncompressedPixelData(*this)); + command->AcquirePayload(new LoadUncompressedPixelData(*this)); Schedule(command.release()); } else @@ -194,7 +194,7 @@ void OrthancMultiframeVolumeLoader::SetGeometry(const Orthanc::DicomMap& dicom) { - DicomInstanceParameters parameters(dicom); + OrthancStone::DicomInstanceParameters parameters(dicom); volume_->SetDicomParameters(parameters); Orthanc::PixelFormat format; @@ -206,7 +206,7 @@ double spacingZ; switch (parameters.GetSopClassUid()) { - case SopClassUid_RTDose: + case OrthancStone::SopClassUid_RTDose: spacingZ = parameters.GetThickness(); break; @@ -221,7 +221,7 @@ const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames(); { - VolumeImageGeometry geometry; + OrthancStone::VolumeImageGeometry geometry; geometry.SetSizeInVoxels(width, height, depth); geometry.SetAxialGeometry(parameters.GetGeometry()); geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(), @@ -235,7 +235,7 @@ - BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_)); + BroadcastMessage(OrthancStone::DicomVolumeImage::GeometryReadyMessage(*volume_)); } @@ -266,7 +266,7 @@ void OrthancMultiframeVolumeLoader::CopyPixelDataAndComputeDistribution( const std::string& pixelData, std::map& distribution) { - ImageBuffer3D& target = volume_->GetPixelData(); + OrthancStone::ImageBuffer3D& target = volume_->GetPixelData(); const unsigned int bpp = target.GetBytesPerPixel(); const unsigned int width = target.GetWidth(); @@ -308,11 +308,11 @@ for (unsigned int z = 0; z < depth; z++) { - ImageBuffer3D::SliceWriter writer(target, VolumeProjection_Axial, z); + OrthancStone::ImageBuffer3D::SliceWriter writer(target, OrthancStone::VolumeProjection_Axial, z); assert(writer.GetAccessor().GetWidth() == width && writer.GetAccessor().GetHeight() == height); - +#if 0 for (unsigned int y = 0; y < height; y++) { assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat())); @@ -329,6 +329,28 @@ source += bpp; } } +#else + // optimized version (fixed) as of 2020-04-15 + unsigned int pitch = writer.GetAccessor().GetPitch(); + T* targetAddrLine = reinterpret_cast(writer.GetAccessor().GetRow(0)); + assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat())); + + for (unsigned int y = 0; y < height; y++) + { + T* targetAddrPix = targetAddrLine; + for (unsigned int x = 0; x < width; x++) + { + CopyPixel(*targetAddrPix, source); + + distribution[*targetAddrPix] += 1; + + targetAddrPix++; + source += bpp; + } + uint8_t* targetAddrLineBytes = reinterpret_cast(targetAddrLine) + pitch; + targetAddrLine = reinterpret_cast(targetAddrLineBytes); + } +#endif } } } @@ -343,7 +365,7 @@ } else { - ImageBuffer3D& target = volume_->GetPixelData(); + OrthancStone::ImageBuffer3D& target = volume_->GetPixelData(); const uint64_t width = target.GetWidth(); const uint64_t height = target.GetHeight(); @@ -494,7 +516,7 @@ volume_->IncrementRevision(); pixelDataLoaded_ = true; - BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_)); + BroadcastMessage(OrthancStone::DicomVolumeImage::ContentUpdatedMessage(*volume_)); } bool OrthancMultiframeVolumeLoader::HasGeometry() const @@ -508,19 +530,17 @@ } OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader( - boost::shared_ptr volume, - IOracle& oracle, - IObservable& oracleObservable, - float outliersHalfRejectionRate) : - LoaderStateMachine(oracle, oracleObservable), - IObservable(oracleObservable.GetBroker()), - volume_(volume), - pixelDataLoaded_(false), - outliersHalfRejectionRate_(outliersHalfRejectionRate), - distributionRawMin_(0), - distributionRawMax_(0), - computedDistributionMin_(0), - computedDistributionMax_(0) + OrthancStone::ILoadersContext& loadersContext, + boost::shared_ptr volume, + float outliersHalfRejectionRate) + : LoaderStateMachine(loadersContext) + , volume_(volume) + , pixelDataLoaded_(false) + , outliersHalfRejectionRate_(outliersHalfRejectionRate) + , distributionRawMin_(0) + , distributionRawMax_(0) + , computedDistributionMin_(0) + , computedDistributionMax_(0) { if (volume.get() == NULL) { @@ -528,12 +548,27 @@ } } + + boost::shared_ptr + OrthancMultiframeVolumeLoader::Create( + OrthancStone::ILoadersContext& loadersContext, + boost::shared_ptr volume, + float outliersHalfRejectionRate /*= 0.0005*/) + { + boost::shared_ptr obj( + new OrthancMultiframeVolumeLoader( + loadersContext, + volume, + outliersHalfRejectionRate)); + obj->LoaderStateMachine::PostConstructor(); + return obj; + } + OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader() { LOG(TRACE) << "OrthancMultiframeVolumeLoader::~OrthancMultiframeVolumeLoader()"; } - - + void OrthancMultiframeVolumeLoader::GetDistributionMinMax (float& minValue, float& maxValue) const { @@ -563,17 +598,17 @@ instanceId_ = instanceId; { - std::unique_ptr command(new OrthancRestApiCommand); + std::unique_ptr command(new OrthancStone::OrthancRestApiCommand); command->SetHttpHeader("Accept-Encoding", "gzip"); command->SetUri("/instances/" + instanceId + "/tags"); - command->SetPayload(new LoadGeometry(*this)); + command->AcquirePayload(new LoadGeometry(*this)); Schedule(command.release()); } { - std::unique_ptr command(new OrthancRestApiCommand); + std::unique_ptr command(new OrthancStone::OrthancRestApiCommand); command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax"); - command->SetPayload(new LoadTransferSyntax(*this)); + command->AcquirePayload(new LoadTransferSyntax(*this)); Schedule(command.release()); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/OrthancMultiframeVolumeLoader.h --- a/Framework/Loaders/OrthancMultiframeVolumeLoader.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.h Wed Apr 22 14:05:47 2020 +0200 @@ -23,6 +23,7 @@ #include "LoaderStateMachine.h" #include "../Volumes/DicomVolumeImage.h" +#include "../Volumes/IGeometryProvider.h" #include @@ -30,7 +31,7 @@ { class OrthancMultiframeVolumeLoader : public LoaderStateMachine, - public IObservable, + public OrthancStone::IObservable, public IGeometryProvider { private: @@ -39,7 +40,7 @@ class LoadTransferSyntax; class LoadUncompressedPixelData; - boost::shared_ptr volume_; + boost::shared_ptr volume_; std::string instanceId_; std::string transferSyntaxUid_; bool pixelDataLoaded_; @@ -89,15 +90,21 @@ void SetUncompressedPixelData(const std::string& pixelData); - virtual bool HasGeometry() const ORTHANC_OVERRIDE; - virtual const VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE; + bool HasGeometry() const; + const OrthancStone::VolumeImageGeometry& GetImageGeometry() const; + protected: + OrthancMultiframeVolumeLoader( + OrthancStone::ILoadersContext& loadersContext, + boost::shared_ptr volume, + float outliersHalfRejectionRate); public: - OrthancMultiframeVolumeLoader(boost::shared_ptr volume, - IOracle& oracle, - IObservable& oracleObservable, - float outliersHalfRejectionRate = 0.0005); - + + static boost::shared_ptr Create( + OrthancStone::ILoadersContext& loadersContext, + boost::shared_ptr volume, + float outliersHalfRejectionRate = 0.0005); + virtual ~OrthancMultiframeVolumeLoader(); bool IsPixelDataLoaded() const diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp --- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,30 +21,35 @@ #include "OrthancSeriesVolumeProgressiveLoader.h" +#include "../StoneException.h" +#include "../Loaders/ILoadersContext.h" +#include "../Loaders/BasicFetchingItemsSorter.h" +#include "../Loaders/BasicFetchingStrategy.h" #include "../Toolbox/GeometryToolbox.h" #include "../Volumes/DicomVolumeImageMPRSlicer.h" -#include "BasicFetchingItemsSorter.h" -#include "BasicFetchingStrategy.h" #include #include + namespace OrthancStone { - class OrthancSeriesVolumeProgressiveLoader::ExtractedSlice : public DicomVolumeImageMPRSlicer::Slice + using OrthancStone::ILoadersContext; + + class OrthancSeriesVolumeProgressiveLoader::ExtractedSlice : public OrthancStone::DicomVolumeImageMPRSlicer::Slice { private: const OrthancSeriesVolumeProgressiveLoader& that_; public: ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that, - const CoordinateSystem3D& plane) : - DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane), + const OrthancStone::CoordinateSystem3D& plane) : + OrthancStone::DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane), that_(that) { if (IsValid()) { - if (GetProjection() == VolumeProjection_Axial) + if (GetProjection() == OrthancStone::VolumeProjection_Axial) { // For coronal and sagittal projections, we take the global // revision of the volume because even if a single slice changes, @@ -54,22 +59,20 @@ } if (that_.strategy_.get() != NULL && - GetProjection() == VolumeProjection_Axial) + GetProjection() == OrthancStone::VolumeProjection_Axial) { that_.strategy_->SetCurrent(GetSliceIndex()); } } } }; - - - void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSlice(size_t index, - const DicomInstanceParameters& reference) const + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::CheckSlice( + size_t index, const OrthancStone::DicomInstanceParameters& reference) const { - const DicomInstanceParameters& slice = *slices_[index]; + const OrthancStone::DicomInstanceParameters& slice = *slices_[index]; - if (!GeometryToolbox::IsParallel( + if (!OrthancStone::GeometryToolbox::IsParallel( reference.GetGeometry().GetNormal(), slice.GetGeometry().GetNormal())) { @@ -90,8 +93,8 @@ "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())) + 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"); @@ -113,7 +116,7 @@ if (slices_.size() != 0) { - const DicomInstanceParameters& reference = *slices_[0]; + const OrthancStone::DicomInstanceParameters& reference = *slices_[0]; for (size_t i = 1; i < slices_.size(); i++) { @@ -157,7 +160,7 @@ // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" // (called with the slices created in LoadGeometry) - void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::ComputeGeometry(SlicesSorter& slices) + void OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::ComputeGeometry(OrthancStone::SlicesSorter& slices) { Clear(); @@ -169,7 +172,7 @@ if (slices.GetSlicesCount() == 0) { - geometry_.reset(new VolumeImageGeometry); + geometry_.reset(new OrthancStone::VolumeImageGeometry); } else { @@ -178,30 +181,39 @@ for (size_t i = 0; i < slices.GetSlicesCount(); i++) { - const DicomInstanceParameters& slice = - dynamic_cast(slices.GetSlicePayload(i)); - slices_.push_back(new DicomInstanceParameters(slice)); + const OrthancStone::DicomInstanceParameters& slice = + dynamic_cast(slices.GetSlicePayload(i)); + slices_.push_back(new OrthancStone::DicomInstanceParameters(slice)); } CheckVolume(); - const double spacingZ = slices.ComputeSpacingBetweenSlices(); - LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; + double spacingZ; + + if (slices.ComputeSpacingBetweenSlices(spacingZ)) + { + LOG(TRACE) << "Computed spacing between slices: " << spacingZ << "mm"; - const DicomInstanceParameters& parameters = *slices_[0]; + const OrthancStone::DicomInstanceParameters& parameters = *slices_[0]; - geometry_.reset(new VolumeImageGeometry); - geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(), - parameters.GetImageInformation().GetHeight(), - static_cast(slices.GetSlicesCount())); - geometry_->SetAxialGeometry(slices.GetSliceGeometry(0)); - geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(), - parameters.GetPixelSpacingY(), spacingZ); + geometry_.reset(new OrthancStone::VolumeImageGeometry); + geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(), + parameters.GetImageInformation().GetHeight(), + static_cast(slices.GetSlicesCount())); + geometry_->SetAxialGeometry(slices.GetSliceGeometry(0)); + geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(), + parameters.GetPixelSpacingY(), spacingZ); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, + "The origins of the slices of a volume image are not regularly spaced"); + } } } - const VolumeImageGeometry& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry() const + const OrthancStone::VolumeImageGeometry& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetImageGeometry() const { if (!HasGeometry()) { @@ -216,7 +228,7 @@ } - const DicomInstanceParameters& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceParameters(size_t index) const + const OrthancStone::DicomInstanceParameters& OrthancSeriesVolumeProgressiveLoader::SeriesGeometry::GetSliceParameters(size_t index) const { CheckSliceIndex(index); return *slices_[index]; @@ -237,8 +249,9 @@ } - static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command) + static unsigned int GetSliceIndexPayload(const OrthancStone::OracleCommandBase& command) { + assert(command.HasPayload()); return dynamic_cast< const Orthanc::SingleValueObject& >(command.GetPayload()).GetValue(); } @@ -247,13 +260,17 @@ { assert(strategy_.get() != NULL); - unsigned int sliceIndex, quality; + unsigned int sliceIndex = 0, quality = 0; if (strategy_->GetNext(sliceIndex, quality)) { - assert(quality <= BEST_QUALITY); + if (!progressiveQuality_) + { + ORTHANC_ASSERT(quality == QUALITY_00, "INTERNAL ERROR. quality != QUALITY_00 in " + << "OrthancSeriesVolumeProgressiveLoader::ScheduleNextSliceDownload"); + } - const DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex); + const OrthancStone::DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex); const std::string& instance = slice.GetOrthancInstanceIdentifier(); if (instance.empty()) @@ -261,11 +278,11 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - std::unique_ptr command; + std::unique_ptr command; - if (quality == BEST_QUALITY) + if (!progressiveQuality_ || quality == QUALITY_02) { - std::unique_ptr tmp(new GetOrthancImageCommand); + std::unique_ptr tmp(new OrthancStone::GetOrthancImageCommand); // TODO: review the following comment. // - Commented out by bgo on 2019-07-19 | reason: Alain has seen cases // where gzipping the uint16 image took 11 sec to produce 5mb. @@ -277,22 +294,36 @@ tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat()); tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); + //LOG(INFO) + // << "OrthancSeriesVolumeProgressiveLoader.ScheduleNextSliceDownload()" + // << " sliceIndex = " << sliceIndex << " slice quality = " << quality + // << " URI = " << tmp->GetUri(); command.reset(tmp.release()); } - else + else // progressive mode is true AND quality is not final (different from QUALITY_02 { - std::unique_ptr tmp(new GetOrthancWebViewerJpegCommand); + std::unique_ptr tmp( + new OrthancStone::GetOrthancWebViewerJpegCommand); + // TODO: review the following comment. Commented out by bgo on 2019-07-19 // (gzip for jpeg seems overkill) //tmp->SetHttpHeader("Accept-Encoding", "gzip"); tmp->SetInstance(instance); - tmp->SetQuality((quality == 0 ? 50 : 90)); + tmp->SetQuality((quality == 0 ? 50 : 90)); // QUALITY_00 is Jpeg50 while QUALITY_01 is Jpeg90 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); + LOG(TRACE) + << "OrthancSeriesVolumeProgressiveLoader.ScheduleNextSliceDownload()" + << " sliceIndex = " << sliceIndex << " slice quality = " << quality; command.reset(tmp.release()); } - command->SetPayload(new Orthanc::SingleValueObject(sliceIndex)); - oracle_.Schedule(*this, command.release()); + command->AcquirePayload(new Orthanc::SingleValueObject(sliceIndex)); + + { + std::unique_ptr lock(loadersContext_.Lock()); + boost::shared_ptr observer(GetSharedObserver()); + lock->Schedule(observer, 0, command.release()); // TODO: priority! + } } else { @@ -305,7 +336,7 @@ /** This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags" */ - void OrthancSeriesVolumeProgressiveLoader::LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message) + void OrthancSeriesVolumeProgressiveLoader::LoadGeometry(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) { Json::Value body; message.ParseJsonBody(body); @@ -318,18 +349,18 @@ { Json::Value::Members instances = body.getMemberNames(); - SlicesSorter slices; + OrthancStone::SlicesSorter slices; for (size_t i = 0; i < instances.size(); i++) { Orthanc::DicomMap dicom; dicom.FromDicomAsJson(body[instances[i]]); - std::unique_ptr instance(new DicomInstanceParameters(dicom)); + std::unique_ptr instance(new OrthancStone::DicomInstanceParameters(dicom)); instance->SetOrthancInstanceIdentifier(instances[i]); // the 3D plane corresponding to the slice - CoordinateSystem3D geometry = instance->GetGeometry(); + OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry(); slices.AddSlice(geometry, instance.release()); } @@ -344,14 +375,22 @@ } else { - const DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0); + const OrthancStone::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(slicesCount)), BEST_QUALITY)); - + // If we are in progressive mode, the Fetching strategy will first request QUALITY_00, then QUALITY_01, then + // QUALITY_02... Otherwise, it's only QUALITY_00 + unsigned int maxQuality = QUALITY_00; + if (progressiveQuality_) + maxQuality = QUALITY_02; + + strategy_.reset(new OrthancStone::BasicFetchingStrategy( + sorter_->CreateSorter(static_cast(slicesCount)), + maxQuality)); + assert(simultaneousDownloads_ != 0); for (unsigned int i = 0; i < simultaneousDownloads_; i++) { @@ -361,7 +400,7 @@ slicesQuality_.resize(slicesCount, 0); - BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_)); + BroadcastMessage(OrthancStone::DicomVolumeImage::GeometryReadyMessage(*volume_)); } @@ -369,13 +408,22 @@ const Orthanc::ImageAccessor& image, unsigned int quality) { - assert(sliceIndex < slicesQuality_.size() && + ORTHANC_ASSERT(sliceIndex < slicesQuality_.size() && slicesQuality_.size() == volume_->GetPixelData().GetDepth()); + if (!progressiveQuality_) + { + ORTHANC_ASSERT(quality == QUALITY_00); + ORTHANC_ASSERT(slicesQuality_[sliceIndex] == QUALITY_00); + } + if (quality >= slicesQuality_[sliceIndex]) { { - ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), VolumeProjection_Axial, sliceIndex); + OrthancStone::ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), + OrthancStone::VolumeProjection_Axial, + sliceIndex); + Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image); } @@ -383,31 +431,42 @@ seriesGeometry_.IncrementSliceRevision(sliceIndex); slicesQuality_[sliceIndex] = quality; - BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_)); + BroadcastMessage(OrthancStone::DicomVolumeImage::ContentUpdatedMessage(*volume_)); } - + LOG(TRACE) << "SetSliceContent sliceIndex = " << sliceIndex << " -- will " + << " now call ScheduleNextSliceDownload()"; ScheduleNextSliceDownload(); } + void OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent( + const OrthancStone::GetOrthancImageCommand::SuccessMessage& message) + { + unsigned int quality = QUALITY_00; + if (progressiveQuality_) + quality = QUALITY_02; - void OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message) - { - SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY); + SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), + message.GetImage(), + quality); } + void OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent( + const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message) + { + ORTHANC_ASSERT(progressiveQuality_, "INTERNAL ERROR: OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent" + << " called while progressiveQuality_ is false!"); - void OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) - { + LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent"; unsigned int quality; - switch (message.GetOrigin().GetQuality()) + switch (dynamic_cast(message.GetOrigin()).GetQuality()) { case 50: - quality = LOW_QUALITY; + quality = QUALITY_00; break; case 90: - quality = MIDDLE_QUALITY; + quality = QUALITY_01; break; default: @@ -417,36 +476,50 @@ SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality); } + OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader( + OrthancStone::ILoadersContext& loadersContext, + boost::shared_ptr volume, + bool progressiveQuality) + : loadersContext_(loadersContext) + , active_(false) + , progressiveQuality_(progressiveQuality) + , simultaneousDownloads_(4) + , volume_(volume) + , sorter_(new OrthancStone::BasicFetchingItemsSorter::Factory) + , volumeImageReadyInHighQuality_(false) + { + } - OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr& volume, - IOracle& oracle, - IObservable& oracleObservable) : - IObserver(oracleObservable.GetBroker()), - IObservable(oracleObservable.GetBroker()), - oracle_(oracle), - oracleObservable_(oracleObservable), - active_(false), - simultaneousDownloads_(4), - volume_(volume), - sorter_(new BasicFetchingItemsSorter::Factory), - volumeImageReadyInHighQuality_(false) + boost::shared_ptr + OrthancSeriesVolumeProgressiveLoader::Create( + OrthancStone::ILoadersContext& loadersContext, + boost::shared_ptr volume, + bool progressiveQuality) { - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry)); + std::auto_ptr lock(loadersContext.Lock()); + + boost::shared_ptr obj( + new OrthancSeriesVolumeProgressiveLoader( + loadersContext, volume, progressiveQuality)); + + obj->Register( + lock->GetOracleObservable(), + &OrthancSeriesVolumeProgressiveLoader::LoadGeometry); - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent)); + obj->Register( + lock->GetOracleObservable(), + &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent); - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent)); + obj->Register( + lock->GetOracleObservable(), + &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent); + + return obj; } + OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader() { - oracleObservable_.Unregister(this); LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()"; } @@ -470,10 +543,8 @@ void OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId) { -// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries seriesId=" << seriesId; if (active_) { -// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries NOT ACTIVE! --> ERROR"; LOG(ERROR) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries(const std::string& seriesId): (active_)"; throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } @@ -481,18 +552,19 @@ { active_ = true; - std::unique_ptr command(new OrthancRestApiCommand); + std::unique_ptr command(new OrthancStone::OrthancRestApiCommand); command->SetUri("/series/" + seriesId + "/instances-tags"); - -// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries about to call oracle_.Schedule"; - oracle_.Schedule(*this, command.release()); -// LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries called oracle_.Schedule"; + { + std::unique_ptr lock(loadersContext_.Lock()); + boost::shared_ptr observer(GetSharedObserver()); + lock->Schedule(observer, 0, command.release()); //TODO: priority! + } } } - IVolumeSlicer::IExtractedSlice* - OrthancSeriesVolumeProgressiveLoader::ExtractSlice(const CoordinateSystem3D& cuttingPlane) + OrthancStone::IVolumeSlicer::IExtractedSlice* + OrthancSeriesVolumeProgressiveLoader::ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) { if (volume_->HasGeometry()) { diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h --- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Wed Apr 22 14:05:47 2020 +0200 @@ -21,8 +21,10 @@ #pragma once +#include "../Loaders/IFetchingItemsSorter.h" +#include "../Loaders/IFetchingStrategy.h" #include "../Messages/IObservable.h" -#include "../Messages/IObserver.h" +#include "../Messages/ObserverBase.h" #include "../Oracle/GetOrthancImageCommand.h" #include "../Oracle/GetOrthancWebViewerJpegCommand.h" #include "../Oracle/IOracle.h" @@ -30,28 +32,30 @@ #include "../Toolbox/SlicesSorter.h" #include "../Volumes/DicomVolumeImage.h" #include "../Volumes/IVolumeSlicer.h" -#include "IFetchingItemsSorter.h" -#include "IFetchingStrategy.h" + +#include "../Volumes/IGeometryProvider.h" + #include namespace OrthancStone { + class ILoadersContext; /** 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, + public OrthancStone::ObserverBase, + public OrthancStone::IObservable, + public OrthancStone::IVolumeSlicer, public IGeometryProvider { private: - static const unsigned int LOW_QUALITY = 0; - static const unsigned int MIDDLE_QUALITY = 1; - static const unsigned int BEST_QUALITY = 2; - + static const unsigned int QUALITY_00 = 0; + static const unsigned int QUALITY_01 = 1; + static const unsigned int QUALITY_02 = 2; + class ExtractedSlice; /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */ @@ -59,7 +63,7 @@ { private: void CheckSlice(size_t index, - const DicomInstanceParameters& reference) const; + const OrthancStone::DicomInstanceParameters& reference) const; void CheckVolume() const; @@ -67,8 +71,8 @@ void CheckSliceIndex(size_t index) const; - std::unique_ptr geometry_; - std::vector slices_; + std::unique_ptr geometry_; + std::vector slices_; std::vector slicesRevision_; public: @@ -77,16 +81,16 @@ Clear(); } - void ComputeGeometry(SlicesSorter& slices); + void ComputeGeometry(OrthancStone::SlicesSorter& slices); virtual bool HasGeometry() const { return geometry_.get() != NULL; } - virtual const VolumeImageGeometry& GetImageGeometry() const; + virtual const OrthancStone::VolumeImageGeometry& GetImageGeometry() const; - const DicomInstanceParameters& GetSliceParameters(size_t index) const; + const OrthancStone::DicomInstanceParameters& GetSliceParameters(size_t index) const; uint64_t GetSliceRevision(size_t index) const; @@ -95,35 +99,43 @@ void ScheduleNextSliceDownload(); - void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message); + void LoadGeometry(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message); void SetSliceContent(unsigned int sliceIndex, const Orthanc::ImageAccessor& image, unsigned int quality); - void LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message); + void LoadBestQualitySliceContent(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message); - void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message); + void LoadJpegSliceContent(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message); - IOracle& oracle_; - IObservable& oracleObservable_; - bool active_; - unsigned int simultaneousDownloads_; - SeriesGeometry seriesGeometry_; - boost::shared_ptr volume_; - std::unique_ptr sorter_; - std::unique_ptr strategy_; - std::vector slicesQuality_; - bool volumeImageReadyInHighQuality_; - - + OrthancStone::ILoadersContext& loadersContext_; + bool active_; + bool progressiveQuality_; + unsigned int simultaneousDownloads_; + SeriesGeometry seriesGeometry_; + boost::shared_ptr volume_; + std::unique_ptr sorter_; + std::unique_ptr strategy_; + + std::vector slicesQuality_; + bool volumeImageReadyInHighQuality_; + + OrthancSeriesVolumeProgressiveLoader( + OrthancStone::ILoadersContext& loadersContext, + boost::shared_ptr volume, + bool progressiveQuality); + public: ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, VolumeImageReadyInHighQuality, OrthancSeriesVolumeProgressiveLoader); - - OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr& volume, - IOracle& oracle, - IObservable& oracleObservable); + /** + See doc for the progressiveQuality_ field + */ + static boost::shared_ptr Create( + OrthancStone::ILoadersContext& context, + boost::shared_ptr volume, + bool progressiveQuality = false); virtual ~OrthancSeriesVolumeProgressiveLoader(); @@ -149,7 +161,7 @@ /** Same remark as HasGeometry */ - const VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE + const OrthancStone::VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE { return seriesGeometry_.GetImageGeometry(); } @@ -160,6 +172,6 @@ take into account this request (this is done in the ExtractedSlice ctor) */ virtual IExtractedSlice* - ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; + ExtractSlice(const OrthancStone::CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/SeriesFramesLoader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/SeriesFramesLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,550 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "SeriesFramesLoader.h" + +#include "../Oracle/ParseDicomFromFileCommand.h" +#include "../Oracle/ParseDicomFromWadoCommand.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include +#endif + +#include +#include +#include +#include + +#include + +namespace OrthancStone +{ + class SeriesFramesLoader::Payload : public Orthanc::IDynamicObject + { + private: + DicomSource source_; + size_t seriesIndex_; + std::string sopInstanceUid_; // Only used for debug purpose + unsigned int quality_; + bool hasWindowing_; + float windowingCenter_; + float windowingWidth_; + std::unique_ptr userPayload_; + + public: + Payload(const DicomSource& source, + size_t seriesIndex, + const std::string& sopInstanceUid, + unsigned int quality, + Orthanc::IDynamicObject* userPayload) : + source_(source), + seriesIndex_(seriesIndex), + sopInstanceUid_(sopInstanceUid), + quality_(quality), + hasWindowing_(false), + userPayload_(userPayload) + { + } + + size_t GetSeriesIndex() const + { + return seriesIndex_; + } + + const std::string& GetSopInstanceUid() const + { + return sopInstanceUid_; + } + + unsigned int GetQuality() const + { + return quality_; + } + + void SetWindowing(float center, + float width) + { + hasWindowing_ = true; + windowingCenter_ = center; + windowingWidth_ = width; + } + + bool HasWindowing() const + { + return hasWindowing_; + } + + float GetWindowingCenter() const + { + if (hasWindowing_) + { + return windowingCenter_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + float GetWindowingWidth() const + { + if (hasWindowing_) + { + return windowingWidth_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + const DicomSource& GetSource() const + { + return source_; + } + + Orthanc::IDynamicObject* GetUserPayload() const + { + return userPayload_.get(); + } + }; + + + SeriesFramesLoader::SeriesFramesLoader(ILoadersContext& context, + LoadedDicomResources& instances, + const std::string& dicomDirPath, + boost::shared_ptr dicomDir) : + context_(context), + frames_(instances), + dicomDirPath_(dicomDirPath), + dicomDir_(dicomDir) + { + } + + + void SeriesFramesLoader::EmitMessage(const Payload& payload, + const Orthanc::ImageAccessor& image) + { + const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(payload.GetSeriesIndex()); + const Orthanc::DicomMap& instance = frames_.GetInstance(payload.GetSeriesIndex()); + size_t frameIndex = frames_.GetFrameIndex(payload.GetSeriesIndex()); + + if (frameIndex >= parameters.GetImageInformation().GetNumberOfFrames() || + payload.GetSopInstanceUid() != parameters.GetSopInstanceUid()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + LOG(TRACE) << "Decoded instance " << payload.GetSopInstanceUid() << ", frame " + << frameIndex << ": " << image.GetWidth() << "x" + << image.GetHeight() << ", " << Orthanc::EnumerationToString(image.GetFormat()) + << ", quality " << payload.GetQuality(); + + FrameLoadedMessage message(*this, frameIndex, payload.GetQuality(), image, instance, parameters, payload.GetUserPayload()); + BroadcastMessage(message); + } + + +#if ORTHANC_ENABLE_DCMTK == 1 + void SeriesFramesLoader::HandleDicom(const Payload& payload, + Orthanc::ParsedDicomFile& dicom) + { + size_t frameIndex = frames_.GetFrameIndex(payload.GetSeriesIndex()); + + std::unique_ptr decoded; + decoded.reset(Orthanc::DicomImageDecoder::Decode( + dicom, + static_cast(frameIndex))); + + if (decoded.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + EmitMessage(payload, *decoded); + } +#endif + + + void SeriesFramesLoader::HandleDicomWebRendered(const Payload& payload, + const std::string& body, + const std::map& headers) + { + assert(payload.GetSource().IsDicomWeb() && + payload.HasWindowing()); + + bool ok = false; + for (std::map::const_iterator it = headers.begin(); + it != headers.end(); ++it) + { + if (boost::iequals("content-type", it->first) && + boost::iequals(Orthanc::MIME_JPEG, it->second)) + { + ok = true; + break; + } + } + + if (!ok) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "The WADO-RS server has not generated a JPEG image on /rendered"); + } + + Orthanc::JpegReader reader; + reader.ReadFromMemory(body); + + switch (reader.GetFormat()) + { + case Orthanc::PixelFormat_RGB24: + EmitMessage(payload, reader); + break; + + case Orthanc::PixelFormat_Grayscale8: + { + const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(payload.GetSeriesIndex()); + + Orthanc::Image scaled(parameters.GetExpectedPixelFormat(), reader.GetWidth(), reader.GetHeight(), false); + Orthanc::ImageProcessing::Convert(scaled, reader); + + float w = payload.GetWindowingWidth(); + if (w <= 0.01f) + { + w = 0.01f; // Prevent division by zero + } + + const float c = payload.GetWindowingCenter(); + const float scaling = w / 255.0f; + const float offset = (c - w / 2.0f) / scaling; + + Orthanc::ImageProcessing::ShiftScale(scaled, offset, scaling, false /* truncation to speed up */); + EmitMessage(payload, scaled); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + +#if ORTHANC_ENABLE_DCMTK == 1 + void SeriesFramesLoader::Handle(const ParseDicomSuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + + const Payload& payload = dynamic_cast(message.GetOrigin().GetPayload()); + if ((payload.GetSource().IsDicomDir() || + payload.GetSource().IsDicomWeb()) && + message.HasPixelData()) + { + HandleDicom(dynamic_cast(message.GetOrigin().GetPayload()), message.GetDicom()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } +#endif + + + void SeriesFramesLoader::Handle(const GetOrthancImageCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + + const Payload& payload = dynamic_cast(message.GetOrigin().GetPayload()); + assert(payload.GetSource().IsOrthanc()); + + EmitMessage(payload, message.GetImage()); + } + + + void SeriesFramesLoader::Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + + const Payload& payload = dynamic_cast(message.GetOrigin().GetPayload()); + assert(payload.GetSource().IsOrthanc()); + + EmitMessage(payload, message.GetImage()); + } + + + void SeriesFramesLoader::Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + // This is to handle "/rendered" in DICOMweb + assert(message.GetOrigin().HasPayload()); + HandleDicomWebRendered(dynamic_cast(message.GetOrigin().GetPayload()), + message.GetAnswer(), message.GetAnswerHeaders()); + } + + + void SeriesFramesLoader::Handle(const HttpCommand::SuccessMessage& message) + { + // This is to handle "/rendered" in DICOMweb + assert(message.GetOrigin().HasPayload()); + HandleDicomWebRendered(dynamic_cast(message.GetOrigin().GetPayload()), + message.GetAnswer(), message.GetAnswerHeaders()); + } + + + void SeriesFramesLoader::GetPreviewWindowing(float& center, + float& width, + size_t index) const + { + const Orthanc::DicomMap& instance = frames_.GetInstance(index); + const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(index); + + if (parameters.HasDefaultWindowing()) + { + // TODO - Handle multiple presets (take the largest width) + center = parameters.GetDefaultWindowingCenter(); + width = parameters.GetDefaultWindowingWidth(); + } + else + { + float a, b; + if (instance.ParseFloat(a, Orthanc::DICOM_TAG_SMALLEST_IMAGE_PIXEL_VALUE) && + instance.ParseFloat(b, Orthanc::DICOM_TAG_LARGEST_IMAGE_PIXEL_VALUE) && + a < b) + { + center = (a + b) / 2.0f; + width = (b - a); + } + else + { + // Cannot infer a suitable windowing from the available tags + center = 128.0f; + width = 256.0f; + } + } + } + + + Orthanc::IDynamicObject& SeriesFramesLoader::FrameLoadedMessage::GetUserPayload() const + { + if (userPayload_) + { + return *userPayload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void SeriesFramesLoader::Factory::SetDicomDir(const std::string& dicomDirPath, + boost::shared_ptr dicomDir) + { + dicomDirPath_ = dicomDirPath; + dicomDir_ = dicomDir; + } + + + boost::shared_ptr SeriesFramesLoader::Factory::Create(ILoadersContext::ILock& stone) + { + boost::shared_ptr loader( + new SeriesFramesLoader(stone.GetContext(), instances_, dicomDirPath_, dicomDir_)); + loader->Register(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->Register(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->Register(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->Register(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); + +#if ORTHANC_ENABLE_DCMTK == 1 + loader->Register(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); +#endif + + return loader; + } + + + void SeriesFramesLoader::ScheduleLoadFrame(int priority, + const DicomSource& source, + size_t index, + unsigned int quality, + Orthanc::IDynamicObject* userPayload) + { + std::unique_ptr protection(userPayload); + + if (index >= frames_.GetFramesCount() || + quality >= source.GetQualityCount()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + const Orthanc::DicomMap& instance = frames_.GetInstance(index); + + std::string sopInstanceUid; + if (!instance.LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Missing SOPInstanceUID in a DICOM instance"); + } + + if (source.IsDicomDir()) + { + if (dicomDir_.get() == NULL) + { + // Should have been set in the factory + throw Orthanc::OrthancException( + Orthanc::ErrorCode_BadSequenceOfCalls, + "SeriesFramesLoader::Factory::SetDicomDir() should have been called"); + } + + assert(quality == 0); + + std::string file; + if (dicomDir_->LookupStringValue(file, sopInstanceUid, Orthanc::DICOM_TAG_REFERENCED_FILE_ID)) + { + std::unique_ptr command(new ParseDicomFromFileCommand(dicomDirPath_, file)); + command->SetPixelDataIncluded(true); + command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release())); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + else + { + LOG(WARNING) << "Missing tag ReferencedFileID in a DICOMDIR entry"; + } + } + else if (source.IsDicomWeb()) + { + std::string studyInstanceUid, seriesInstanceUid; + if (!instance.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || + !instance.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Missing StudyInstanceUID or SeriesInstanceUID in a DICOM instance"); + } + + const std::string uri = ("/studies/" + studyInstanceUid + + "/series/" + seriesInstanceUid + + "/instances/" + sopInstanceUid); + + if (source.HasDicomWebRendered() && + quality == 0) + { + float c, w; + GetPreviewWindowing(c, w, index); + + std::map arguments, headers; + arguments["window"] = (boost::lexical_cast(c) + "," + + boost::lexical_cast(w) + ",linear"); + headers["Accept"] = "image/jpeg"; + + std::unique_ptr payload(new Payload(source, index, sopInstanceUid, quality, protection.release())); + payload->SetWindowing(c, w); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, + source.CreateDicomWebCommand(uri + "/rendered", arguments, headers, payload.release())); + } + } + else + { + assert((source.HasDicomWebRendered() && quality == 1) || + (!source.HasDicomWebRendered() && quality == 0)); + +#if ORTHANC_ENABLE_DCMTK == 1 + std::unique_ptr payload(new Payload(source, index, sopInstanceUid, quality, protection.release())); + + const std::map empty; + + std::unique_ptr command( + new ParseDicomFromWadoCommand(sopInstanceUid, source.CreateDicomWebCommand(uri, empty, empty, NULL))); + command->AcquirePayload(payload.release()); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "DCMTK is not enabled, cannot parse a DICOM instance"); +#endif + } + } + else if (source.IsOrthanc()) + { + std::string orthancId; + + { + std::string patientId, studyInstanceUid, seriesInstanceUid; + if (!instance.LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) || + !instance.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || + !instance.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Missing StudyInstanceUID or SeriesInstanceUID in a DICOM instance"); + } + + Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid); + orthancId = hasher.HashInstance(); + } + + const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(index); + + if (quality == 0 && source.HasOrthancWebViewer1()) + { + std::unique_ptr command(new GetOrthancWebViewerJpegCommand); + command->SetInstance(orthancId); + command->SetExpectedPixelFormat(parameters.GetExpectedPixelFormat()); + command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release())); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + else if (quality == 0 && source.HasOrthancAdvancedPreview()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + else + { + assert(quality <= 1); + assert(quality == 0 || + source.HasOrthancWebViewer1() || + source.HasOrthancAdvancedPreview()); + + std::unique_ptr command(new GetOrthancImageCommand); + command->SetFrameUri(orthancId, frames_.GetFrameIndex(index), parameters.GetExpectedPixelFormat()); + command->SetExpectedPixelFormat(parameters.GetExpectedPixelFormat()); + command->SetHttpHeader("Accept", Orthanc::MIME_PAM); + command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release())); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/SeriesFramesLoader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/SeriesFramesLoader.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,176 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + +#include "OracleScheduler.h" +#include "DicomSource.h" +#include "SeriesOrderedFrames.h" +#include "ILoaderFactory.h" + +namespace OrthancStone +{ + class SeriesFramesLoader : + public ObserverBase, + public IObservable + { + private: + class Payload; + + ILoadersContext& context_; + SeriesOrderedFrames frames_; + std::string dicomDirPath_; + boost::shared_ptr dicomDir_; + + SeriesFramesLoader(ILoadersContext& context, + LoadedDicomResources& instances, + const std::string& dicomDirPath, + boost::shared_ptr dicomDir); + + void EmitMessage(const Payload& payload, + const Orthanc::ImageAccessor& image); + +#if ORTHANC_ENABLE_DCMTK == 1 + void HandleDicom(const Payload& payload, + Orthanc::ParsedDicomFile& dicom); +#endif + + void HandleDicomWebRendered(const Payload& payload, + const std::string& body, + const std::map& headers); + +#if ORTHANC_ENABLE_DCMTK == 1 + void Handle(const ParseDicomSuccessMessage& message); +#endif + + void Handle(const GetOrthancImageCommand::SuccessMessage& message); + + void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message); + + void Handle(const OrthancRestApiCommand::SuccessMessage& message); + + void Handle(const HttpCommand::SuccessMessage& message); + + void GetPreviewWindowing(float& center, + float& width, + size_t index) const; + + public: + class FrameLoadedMessage : public OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + size_t frameIndex_; + unsigned int quality_; + const Orthanc::ImageAccessor& image_; + const Orthanc::DicomMap& instance_; + const DicomInstanceParameters& parameters_; + Orthanc::IDynamicObject* userPayload_; // Ownership is maintained by the caller + + public: + FrameLoadedMessage(const SeriesFramesLoader& loader, + size_t frameIndex, + unsigned int quality, + const Orthanc::ImageAccessor& image, + const Orthanc::DicomMap& instance, + const DicomInstanceParameters& parameters, + Orthanc::IDynamicObject* userPayload) : + OriginMessage(loader), + frameIndex_(frameIndex), + quality_(quality), + image_(image), + instance_(instance), + parameters_(parameters), + userPayload_(userPayload) + { + } + + size_t GetFrameIndex() const + { + return frameIndex_; + } + + unsigned int GetQuality() const + { + return quality_; + } + + const Orthanc::ImageAccessor& GetImage() const + { + return image_; + } + + const Orthanc::DicomMap& GetInstance() const + { + return instance_; + } + + const DicomInstanceParameters& GetInstanceParameters() const + { + return parameters_; + } + + bool HasUserPayload() const + { + return userPayload_ != NULL; + } + + Orthanc::IDynamicObject& GetUserPayload() const; + }; + + + class Factory : public ILoaderFactory + { + private: + LoadedDicomResources& instances_; + std::string dicomDirPath_; + boost::shared_ptr dicomDir_; + + public: + // No "const" because "LoadedDicomResources::GetResource()" will call "Flatten()" + Factory(LoadedDicomResources& instances) : + instances_(instances) + { + } + + void SetDicomDir(const std::string& dicomDirPath, + boost::shared_ptr dicomDir); + + virtual boost::shared_ptr Create(ILoadersContext::ILock& context); + }; + + const SeriesOrderedFrames& GetOrderedFrames() const + { + return frames_; + } + + void ScheduleLoadFrame(int priority, + const DicomSource& source, + size_t index, + unsigned int quality, + Orthanc::IDynamicObject* userPayload /* transfer ownership */); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/SeriesMetadataLoader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/SeriesMetadataLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,348 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "SeriesMetadataLoader.h" + +#include + +namespace OrthancStone +{ + SeriesMetadataLoader::SeriesMetadataLoader(boost::shared_ptr& loader) : + loader_(loader), + state_(State_Setup) + { + } + + + bool SeriesMetadataLoader::IsScheduledWithHigherPriority(const std::string& seriesInstanceUid, + int priority) const + { + if (series_.find(seriesInstanceUid) != series_.end()) + { + // This series is readily available + return true; + } + else + { + std::map::const_iterator found = scheduled_.find(seriesInstanceUid); + + return (found != scheduled_.end() && + found->second < priority); + } + } + + + void SeriesMetadataLoader::Handle(const DicomResourcesLoader::SuccessMessage& message) + { + assert(message.GetResources()); + + switch (state_) + { + case State_Setup: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + + case State_Default: + { + std::string studyInstanceUid; + std::string seriesInstanceUid; + + if (message.GetResources()->LookupTagValueConsensus(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID) && + message.GetResources()->LookupTagValueConsensus(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID)) + { + series_[seriesInstanceUid] = message.GetResources(); + + SeriesLoadedMessage loadedMessage(*this, message.GetDicomSource(), studyInstanceUid, + seriesInstanceUid, *message.GetResources()); + BroadcastMessage(loadedMessage); + } + + break; + } + + case State_DicomDir: + { + assert(!dicomDir_); + assert(seriesSize_.empty()); + + dicomDir_ = message.GetResources(); + + for (size_t i = 0; i < message.GetResources()->GetSize(); i++) + { + std::string seriesInstanceUid; + if (message.GetResources()->GetResource(i).LookupStringValue + (seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) + { + boost::shared_ptr target + (new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SOP_INSTANCE_UID)); + + if (loader_->ScheduleLoadDicomFile(target, message.GetPriority(), message.GetDicomSource(), dicomDirPath_, + message.GetResources()->GetResource(i), false /* no need for pixel data */, + NULL /* TODO PAYLOAD */)) + { + std::map::iterator found = seriesSize_.find(seriesInstanceUid); + if (found == seriesSize_.end()) + { + series_[seriesInstanceUid].reset + (new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SOP_INSTANCE_UID)); + seriesSize_[seriesInstanceUid] = 1; + } + else + { + found->second ++; + } + } + } + } + + LOG(INFO) << "Read a DICOMDIR containing " << seriesSize_.size() << " series"; + + state_ = State_DicomFile; + break; + } + + case State_DicomFile: + { + assert(dicomDir_); + assert(message.GetResources()->GetSize() <= 1); // Could be zero if corrupted DICOM instance + + if (message.GetResources()->GetSize() == 1) + { + const Orthanc::DicomMap& instance = message.GetResources()->GetResource(0); + + std::string studyInstanceUid; + std::string seriesInstanceUid; + if (instance.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && + instance.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) + { + Series::const_iterator series = series_.find(seriesInstanceUid); + std::map::const_iterator size = seriesSize_.find(seriesInstanceUid); + + if (series == series_.end() || + size == seriesSize_.end()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + series->second->AddResource(instance); + + if (series->second->GetSize() > size->second) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else if (series->second->GetSize() == size->second) + { + // The series is complete + SeriesLoadedMessage loadedMessage( + *this, message.GetDicomSource(), + studyInstanceUid, seriesInstanceUid, *series->second); + loadedMessage.SetDicomDir(dicomDirPath_, dicomDir_); + BroadcastMessage(loadedMessage); + } + } + } + } + + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + SeriesMetadataLoader::SeriesLoadedMessage::SeriesLoadedMessage( + const SeriesMetadataLoader& loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + LoadedDicomResources& instances) : + OriginMessage(loader), + source_(source), + studyInstanceUid_(studyInstanceUid), + seriesInstanceUid_(seriesInstanceUid), + instances_(instances) + { + LOG(INFO) << "Loaded series " << seriesInstanceUid + << ", number of instances: " << instances_.GetSize(); + } + + + boost::shared_ptr SeriesMetadataLoader::Factory::Create(ILoadersContext::ILock& context) + { + DicomResourcesLoader::Factory factory; + boost::shared_ptr loader + (boost::dynamic_pointer_cast(factory.Create(context))); + + boost::shared_ptr obj(new SeriesMetadataLoader(loader)); + obj->Register(*loader, &SeriesMetadataLoader::Handle); + return obj; + } + + + SeriesMetadataLoader::Accessor::Accessor(SeriesMetadataLoader& that, + const std::string& seriesInstanceUid) + { + Series::const_iterator found = that.series_.find(seriesInstanceUid); + if (found != that.series_.end()) + { + assert(found->second != NULL); + series_ = found->second; + } + } + + + size_t SeriesMetadataLoader::Accessor::GetInstancesCount() const + { + if (IsComplete()) + { + return series_->GetSize(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + const Orthanc::DicomMap& SeriesMetadataLoader::Accessor::GetInstance(size_t index) const + { + if (IsComplete()) + { + return series_->GetResource(index); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void SeriesMetadataLoader::ScheduleLoadSeries(int priority, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) + { + if (state_ != State_Setup && + state_ != State_Default) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "The loader is working in DICOMDIR state"); + } + + state_ = State_Default; + + // Only re-schedule the loading if the previous loading was with lower priority + if (!IsScheduledWithHigherPriority(seriesInstanceUid, priority)) + { + if (source.IsDicomWeb()) + { + boost::shared_ptr target + (new LoadedDicomResources(Orthanc::DICOM_TAG_SOP_INSTANCE_UID)); + loader_->ScheduleGetDicomWeb( + target, priority, source, + "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/metadata", + NULL /* TODO PAYLOAD */); + + scheduled_[seriesInstanceUid] = priority; + } + else if (source.IsOrthanc()) + { + // This flavor of the method is only available with DICOMweb, as + // Orthanc requires the "PatientID" to be known + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "The PatientID must be provided on Orthanc sources"); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + + + void SeriesMetadataLoader::ScheduleLoadSeries(int priority, + const DicomSource& source, + const std::string& patientId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) + { + if (state_ != State_Setup && + state_ != State_Default) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "The loader is working in DICOMDIR state"); + } + + state_ = State_Default; + + if (source.IsDicomWeb()) + { + ScheduleLoadSeries(priority, source, studyInstanceUid, seriesInstanceUid); + } + else if (!IsScheduledWithHigherPriority(seriesInstanceUid, priority)) + { + if (source.IsOrthanc()) + { + // Dummy SOP Instance UID, as we are working at the "series" level + Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, "dummy"); + + boost::shared_ptr target + (new LoadedDicomResources(Orthanc::DICOM_TAG_SOP_INSTANCE_UID)); + + loader_->ScheduleLoadOrthancResources(target, priority, source, Orthanc::ResourceType_Series, + hasher.HashSeries(), Orthanc::ResourceType_Instance, + NULL /* TODO PAYLOAD */); + + scheduled_[seriesInstanceUid] = priority; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + + + void SeriesMetadataLoader::ScheduleLoadDicomDir(int priority, + const DicomSource& source, + const std::string& path) + { + if (!source.IsDicomDir()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "The loader cannot load two different DICOMDIR"); + } + + state_ = State_DicomDir; + dicomDirPath_ = path; + boost::shared_ptr dicomDir + (new LoadedDicomResources(Orthanc::DICOM_TAG_REFERENCED_SOP_INSTANCE_UID_IN_FILE)); + loader_->ScheduleLoadDicomDir(dicomDir, priority, source, path, + NULL /* TODO PAYLOAD */); + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/SeriesMetadataLoader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/SeriesMetadataLoader.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "DicomResourcesLoader.h" + +namespace OrthancStone +{ + class SeriesMetadataLoader : + public ObserverBase, + public IObservable + { + private: + enum State + { + State_Setup, + State_Default, + State_DicomDir, + State_DicomFile + }; + + typedef std::map > Series; + + boost::shared_ptr loader_; + State state_; + std::map scheduled_; // Maps a "SeriesInstanceUID" to a priority + Series series_; + boost::shared_ptr dicomDir_; + std::string dicomDirPath_; + std::map seriesSize_; + + SeriesMetadataLoader(boost::shared_ptr& loader); + + bool IsScheduledWithHigherPriority(const std::string& seriesInstanceUid, + int priority) const; + + void Handle(const DicomResourcesLoader::SuccessMessage& message); + + public: + class SeriesLoadedMessage : public OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const DicomSource& source_; + const std::string& studyInstanceUid_; + const std::string& seriesInstanceUid_; + LoadedDicomResources& instances_; + std::string dicomDirPath_; + boost::shared_ptr dicomDir_; + + public: + SeriesLoadedMessage(const SeriesMetadataLoader& loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + LoadedDicomResources& instances); + + const DicomSource& GetDicomSource() const + { + return source_; + } + + const std::string& GetStudyInstanceUid() const + { + return studyInstanceUid_; + } + + const std::string& GetSeriesInstanceUid() const + { + return seriesInstanceUid_; + } + + size_t GetInstancesCount() const + { + return instances_.GetSize(); + } + + const Orthanc::DicomMap& GetInstance(size_t index) const + { + return instances_.GetResource(index); + } + + LoadedDicomResources& GetInstances() const + { + return instances_; + } + + void SetDicomDir(const std::string& dicomDirPath, + boost::shared_ptr dicomDir) + { + dicomDirPath_ = dicomDirPath; + dicomDir_ = dicomDir; + } + + const std::string& GetDicomDirPath() const + { + return dicomDirPath_; + } + + // Will be NULL on non-DICOMDIR sources + boost::shared_ptr GetDicomDir() const + { + return dicomDir_; + } + }; + + + class Factory : public ILoaderFactory + { + public: + virtual boost::shared_ptr Create(ILoadersContext::ILock& context); + }; + + + class Accessor : public boost::noncopyable + { + private: + boost::shared_ptr series_; + + public: + Accessor(SeriesMetadataLoader& that, + const std::string& seriesInstanceUid); + + bool IsComplete() const + { + return series_ != NULL; + } + + size_t GetInstancesCount() const; + + const Orthanc::DicomMap& GetInstance(size_t index) const; + }; + + + void ScheduleLoadSeries(int priority, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid); + + void ScheduleLoadSeries(int priority, + const DicomSource& source, + const std::string& patientId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid); + + void ScheduleLoadDicomDir(int priority, + const DicomSource& source, + const std::string& path); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/SeriesOrderedFrames.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/SeriesOrderedFrames.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,345 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "../Toolbox/SlicesSorter.h" +#include "SeriesOrderedFrames.h" + +#include + +namespace OrthancStone +{ + class SeriesOrderedFrames::Instance : public boost::noncopyable + { + private: + std::unique_ptr dicom_; + DicomInstanceParameters parameters_; + + public: + Instance(const Orthanc::DicomMap& dicom) : + dicom_(dicom.Clone()), + parameters_(dicom) + { + } + + const Orthanc::DicomMap& GetInstance() const + { + return *dicom_; + } + + const DicomInstanceParameters& GetInstanceParameters() const + { + return parameters_; + } + + bool Lookup3DGeometry(CoordinateSystem3D& target) const + { + try + { + std::string imagePositionPatient, imageOrientationPatient; + if (dicom_->LookupStringValue(imagePositionPatient, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && + dicom_->LookupStringValue(imageOrientationPatient, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) + { + target = CoordinateSystem3D(imagePositionPatient, imageOrientationPatient); + return true; + } + } + catch (Orthanc::OrthancException&) + { + } + + return false; + } + + bool LookupIndexInSeries(int& target) const + { + std::string value; + + if (dicom_->LookupStringValue(value, Orthanc::DICOM_TAG_INSTANCE_NUMBER, false) || + dicom_->LookupStringValue(value, Orthanc::DICOM_TAG_IMAGE_INDEX, false)) + { + try + { + target = boost::lexical_cast(value); + return true; + } + catch (boost::bad_lexical_cast&) + { + } + } + + return false; + } + }; + + + class SeriesOrderedFrames::Frame : public boost::noncopyable + { + private: + const Instance* instance_; + unsigned int frameIndex_; + + public: + Frame(const Instance& instance, + unsigned int frameIndex) : + instance_(&instance), + frameIndex_(frameIndex) + { + if (frameIndex_ >= instance.GetInstanceParameters().GetImageInformation().GetNumberOfFrames()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + const Orthanc::DicomMap& GetInstance() const + { + assert(instance_ != NULL); + return instance_->GetInstance(); + } + + const DicomInstanceParameters& GetInstanceParameters() const + { + assert(instance_ != NULL); + return instance_->GetInstanceParameters(); + } + + unsigned int GetFrameIndex() const + { + return frameIndex_; + } + }; + + + class SeriesOrderedFrames::InstanceWithIndexInSeries + { + private: + const Instance* instance_; // Don't use a reference to make "std::sort()" happy + int index_; + + public: + InstanceWithIndexInSeries(const Instance& instance) : + instance_(&instance) + { + if (!instance_->LookupIndexInSeries(index_)) + { + index_ = std::numeric_limits::max(); + } + } + + const Instance& GetInstance() const + { + return *instance_; + } + + int GetIndexInSeries() const + { + return index_; + } + + bool operator< (const InstanceWithIndexInSeries& other) const + { + return (index_ < other.index_); + } + }; + + + void SeriesOrderedFrames::Clear() + { + for (size_t i = 0; i < instances_.size(); i++) + { + assert(instances_[i] != NULL); + delete instances_[i]; + } + + for (size_t i = 0; i < orderedFrames_.size(); i++) + { + assert(orderedFrames_[i] != NULL); + delete orderedFrames_[i]; + } + + instances_.clear(); + orderedFrames_.clear(); + } + + + bool SeriesOrderedFrames::Sort3DVolume() + { + SlicesSorter sorter; + sorter.Reserve(instances_.size()); + + for (size_t i = 0; i < instances_.size(); i++) + { + CoordinateSystem3D geometry; + if (instances_[i]->Lookup3DGeometry(geometry)) + { + sorter.AddSlice(geometry, new Orthanc::SingleValueObject(instances_[i])); + } + else + { + return false; // Not a 3D volume + } + } + + if (!sorter.Sort() || + sorter.GetSlicesCount() != instances_.size() || + !sorter.AreAllSlicesDistinct()) + { + return false; + } + else + { + for (size_t i = 0; i < sorter.GetSlicesCount(); i++) + { + assert(sorter.HasSlicePayload(i)); + + const Orthanc::SingleValueObject& payload = + dynamic_cast&>(sorter.GetSlicePayload(i)); + + assert(payload.GetValue() != NULL); + + for (size_t j = 0; j < payload.GetValue()->GetInstanceParameters().GetImageInformation().GetNumberOfFrames(); j++) + { + orderedFrames_.push_back(new Frame(*payload.GetValue(), + static_cast(j))); + } + } + + isRegular_ = sorter.ComputeSpacingBetweenSlices(spacingBetweenSlices_); + return true; + } + } + + + void SeriesOrderedFrames::SortIndexInSeries() + { + std::vector tmp; + tmp.reserve(instances_.size()); + + for (size_t i = 0; i < instances_.size(); i++) + { + assert(instances_[i] != NULL); + tmp.push_back(InstanceWithIndexInSeries(*instances_[i])); + } + + std::sort(tmp.begin(), tmp.end()); + + for (size_t i = 0; i < tmp.size(); i++) + { + for (size_t j = 0; j < tmp[i].GetInstance().GetInstanceParameters().GetImageInformation().GetNumberOfFrames(); j++) + { + orderedFrames_.push_back(new Frame(tmp[i].GetInstance(), + static_cast(j))); + } + } + } + + + const SeriesOrderedFrames::Frame& SeriesOrderedFrames::GetFrame(size_t seriesIndex) const + { + if (seriesIndex >= orderedFrames_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + assert(orderedFrames_[seriesIndex] != NULL); + return *(orderedFrames_[seriesIndex]); + } + } + + + SeriesOrderedFrames::SeriesOrderedFrames(LoadedDicomResources& instances) : + isVolume_(false), + isRegular_(false), + spacingBetweenSlices_(0) + { + instances_.reserve(instances.GetSize()); + + size_t numberOfFrames = 0; + + for (size_t i = 0; i < instances.GetSize(); i++) + { + try + { + std::unique_ptr instance(new Instance(instances.GetResource(i))); + numberOfFrames += instance->GetInstanceParameters().GetImageInformation().GetNumberOfFrames(); + instances_.push_back(instance.release()); + } + catch (Orthanc::OrthancException&) + { + // The instance has not all the required DICOM tags, skip it + } + } + + orderedFrames_.reserve(numberOfFrames); + + if (Sort3DVolume()) + { + isVolume_ = true; + + if (isRegular_) + { + LOG(INFO) << "Regular 3D volume detected"; + } + else + { + LOG(INFO) << "Non-regular 3D volume detected"; + } + } + else + { + LOG(INFO) << "Series is not a 3D volume, sorting by index"; + SortIndexInSeries(); + } + + LOG(INFO) << "Number of frames: " << orderedFrames_.size(); + } + + + unsigned int SeriesOrderedFrames::GetFrameIndex(size_t seriesIndex) const + { + return GetFrame(seriesIndex).GetFrameIndex(); + } + + + const Orthanc::DicomMap& SeriesOrderedFrames::GetInstance(size_t seriesIndex) const + { + return GetFrame(seriesIndex).GetInstance(); + } + + + const DicomInstanceParameters& SeriesOrderedFrames::GetInstanceParameters(size_t seriesIndex) const + { + return GetFrame(seriesIndex).GetInstanceParameters(); + } + + + double SeriesOrderedFrames::GetSpacingBetweenSlices() const + { + if (IsRegular3DVolume()) + { + return spacingBetweenSlices_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/SeriesOrderedFrames.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/SeriesOrderedFrames.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,85 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "LoadedDicomResources.h" + +#include "../Toolbox/DicomInstanceParameters.h" + +namespace OrthancStone +{ + class SeriesOrderedFrames : public boost::noncopyable + { + private: + class Instance; + class Frame; + class InstanceWithIndexInSeries; + + std::vector instances_; + std::vector orderedFrames_; + bool isVolume_; + bool isRegular_; + double spacingBetweenSlices_; + + void Clear(); + + bool Sort3DVolume(); + + void SortIndexInSeries(); + + const Frame& GetFrame(size_t seriesIndex) const; + + public: + SeriesOrderedFrames(LoadedDicomResources& instances); + + ~SeriesOrderedFrames() + { + Clear(); + } + + size_t GetFramesCount() const + { + return orderedFrames_.size(); + } + + unsigned int GetFrameIndex(size_t seriesIndex) const; + + const Orthanc::DicomMap& GetInstance(size_t seriesIndex) const; + + const DicomInstanceParameters& GetInstanceParameters(size_t seriesIndex) const; + + // Are all frames parallel and aligned? + bool Is3DVolume() const + { + return isVolume_; + } + + // Are all frames parallel, aligned and evenly spaced? + bool IsRegular3DVolume() const + { + return isRegular_; + } + + // Only available on regular 3D volumes + double GetSpacingBetweenSlices() const; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/SeriesThumbnailsLoader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/SeriesThumbnailsLoader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,566 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "SeriesThumbnailsLoader.h" + +#include +#include +#include +#include +#include + +#include + +static const unsigned int JPEG_QUALITY = 70; // Only used for Orthanc source + +namespace OrthancStone +{ + static SeriesThumbnailType ExtractSopClassUid(const std::string& sopClassUid) + { + if (sopClassUid == "1.2.840.10008.5.1.4.1.1.104.1") // Encapsulated PDF Storage + { + return SeriesThumbnailType_Pdf; + } + else if (sopClassUid == "1.2.840.10008.5.1.4.1.1.77.1.1.1" || // Video Endoscopic Image Storage + sopClassUid == "1.2.840.10008.5.1.4.1.1.77.1.2.1" || // Video Microscopic Image Storage + sopClassUid == "1.2.840.10008.5.1.4.1.1.77.1.4.1") // Video Photographic Image Storage + { + return SeriesThumbnailType_Video; + } + else + { + return SeriesThumbnailType_Unsupported; + } + } + + + SeriesThumbnailsLoader::Thumbnail::Thumbnail(const std::string& image, + const std::string& mime) : + type_(SeriesThumbnailType_Image), + image_(image), + mime_(mime) + { + } + + + SeriesThumbnailsLoader::Thumbnail::Thumbnail(SeriesThumbnailType type) : + type_(type) + { + if (type == SeriesThumbnailType_Image) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + void SeriesThumbnailsLoader::AcquireThumbnail(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + SeriesThumbnailsLoader::Thumbnail* thumbnail) + { + assert(thumbnail != NULL); + + std::unique_ptr protection(thumbnail); + + Thumbnails::iterator found = thumbnails_.find(seriesInstanceUid); + if (found == thumbnails_.end()) + { + thumbnails_[seriesInstanceUid] = protection.release(); + } + else + { + assert(found->second != NULL); + delete found->second; + found->second = protection.release(); + } + + ThumbnailLoadedMessage message(*this, source, studyInstanceUid, seriesInstanceUid, *thumbnail); + BroadcastMessage(message); + } + + + class SeriesThumbnailsLoader::Handler : public Orthanc::IDynamicObject + { + private: + boost::shared_ptr loader_; + DicomSource source_; + std::string studyInstanceUid_; + std::string seriesInstanceUid_; + + public: + Handler(boost::shared_ptr loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + loader_(loader), + source_(source), + studyInstanceUid_(studyInstanceUid), + seriesInstanceUid_(seriesInstanceUid) + { + if (!loader) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + boost::shared_ptr GetLoader() + { + return loader_; + } + + const DicomSource& GetSource() const + { + return source_; + } + + const std::string& GetStudyInstanceUid() const + { + return studyInstanceUid_; + } + + const std::string& GetSeriesInstanceUid() const + { + return seriesInstanceUid_; + } + + virtual void HandleSuccess(const std::string& body, + const std::map& headers) = 0; + + virtual void HandleError() + { + LOG(INFO) << "Cannot generate thumbnail for SeriesInstanceUID: " << seriesInstanceUid_; + } + }; + + + class SeriesThumbnailsLoader::DicomWebSopClassHandler : public SeriesThumbnailsLoader::Handler + { + private: + static bool GetSopClassUid(std::string& sopClassUid, + const Json::Value& json) + { + Orthanc::DicomMap dicom; + dicom.FromDicomWeb(json); + + return dicom.LookupStringValue(sopClassUid, Orthanc::DICOM_TAG_SOP_CLASS_UID, false); + } + + public: + DicomWebSopClassHandler(boost::shared_ptr loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + Handler(loader, source, studyInstanceUid, seriesInstanceUid) + { + } + + virtual void HandleSuccess(const std::string& body, + const std::map& headers) + { + Json::Reader reader; + Json::Value value; + + if (!reader.parse(body, value) || + value.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + else + { + SeriesThumbnailType type = SeriesThumbnailType_Unsupported; + + std::string sopClassUid; + if (value.size() > 0 && + GetSopClassUid(sopClassUid, value[0])) + { + bool ok = true; + + for (Json::Value::ArrayIndex i = 1; i < value.size() && ok; i++) + { + std::string s; + if (!GetSopClassUid(s, value[i]) || + s != sopClassUid) + { + ok = false; + } + } + + if (ok) + { + type = ExtractSopClassUid(sopClassUid); + } + } + + GetLoader()->AcquireThumbnail(GetSource(), GetStudyInstanceUid(), + GetSeriesInstanceUid(), new Thumbnail(type)); + } + } + }; + + + class SeriesThumbnailsLoader::DicomWebThumbnailHandler : public SeriesThumbnailsLoader::Handler + { + public: + DicomWebThumbnailHandler(boost::shared_ptr loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + Handler(loader, source, studyInstanceUid, seriesInstanceUid) + { + } + + virtual void HandleSuccess(const std::string& body, + const std::map& headers) + { + std::string mime = Orthanc::MIME_JPEG; + for (std::map::const_iterator + it = headers.begin(); it != headers.end(); ++it) + { + if (boost::iequals(it->first, "content-type")) + { + mime = it->second; + } + } + + GetLoader()->AcquireThumbnail(GetSource(), GetStudyInstanceUid(), + GetSeriesInstanceUid(), new Thumbnail(body, mime)); + } + + virtual void HandleError() + { + // The DICOMweb wasn't able to generate a thumbnail, try to + // retrieve the SopClassUID tag using QIDO-RS + + std::map arguments, headers; + arguments["0020000D"] = GetStudyInstanceUid(); + arguments["0020000E"] = GetSeriesInstanceUid(); + arguments["includefield"] = "00080016"; + + std::unique_ptr command( + GetSource().CreateDicomWebCommand( + "/instances", arguments, headers, new DicomWebSopClassHandler( + GetLoader(), GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid()))); + GetLoader()->Schedule(command.release()); + } + }; + + + class SeriesThumbnailsLoader::ThumbnailInformation : public Orthanc::IDynamicObject + { + private: + DicomSource source_; + std::string studyInstanceUid_; + std::string seriesInstanceUid_; + + public: + ThumbnailInformation(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + source_(source), + studyInstanceUid_(studyInstanceUid), + seriesInstanceUid_(seriesInstanceUid) + { + } + + const DicomSource& GetDicomSource() const + { + return source_; + } + + const std::string& GetStudyInstanceUid() const + { + return studyInstanceUid_; + } + + const std::string& GetSeriesInstanceUid() const + { + return seriesInstanceUid_; + } + }; + + + class SeriesThumbnailsLoader::OrthancSopClassHandler : public SeriesThumbnailsLoader::Handler + { + private: + std::string instanceId_; + + public: + OrthancSopClassHandler(boost::shared_ptr loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& instanceId) : + Handler(loader, source, studyInstanceUid, seriesInstanceUid), + instanceId_(instanceId) + { + } + + virtual void HandleSuccess(const std::string& body, + const std::map& headers) + { + SeriesThumbnailType type = ExtractSopClassUid(body); + + if (type == SeriesThumbnailType_Pdf || + type == SeriesThumbnailType_Video) + { + GetLoader()->AcquireThumbnail(GetSource(), GetStudyInstanceUid(), + GetSeriesInstanceUid(), new Thumbnail(type)); + } + else + { + std::unique_ptr command(new GetOrthancImageCommand); + command->SetUri("/instances/" + instanceId_ + "/preview"); + command->SetHttpHeader("Accept", Orthanc::MIME_JPEG); + command->AcquirePayload(new ThumbnailInformation( + GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid())); + GetLoader()->Schedule(command.release()); + } + } + }; + + + class SeriesThumbnailsLoader::SelectOrthancInstanceHandler : public SeriesThumbnailsLoader::Handler + { + public: + SelectOrthancInstanceHandler(boost::shared_ptr loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + Handler(loader, source, studyInstanceUid, seriesInstanceUid) + { + } + + virtual void HandleSuccess(const std::string& body, + const std::map& headers) + { + static const char* const INSTANCES = "Instances"; + + Json::Value json; + Json::Reader reader; + if (!reader.parse(body, json) || + json.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + if (json.isMember(INSTANCES) && + json[INSTANCES].type() == Json::arrayValue && + json[INSTANCES].size() > 0) + { + // Select one instance of the series to generate the thumbnail + Json::Value::ArrayIndex index = json[INSTANCES].size() / 2; + if (json[INSTANCES][index].type() == Json::stringValue) + { + std::map arguments, headers; + arguments["quality"] = boost::lexical_cast(JPEG_QUALITY); + headers["Accept"] = Orthanc::MIME_JPEG; + + const std::string instance = json[INSTANCES][index].asString(); + + std::unique_ptr command(new OrthancRestApiCommand); + command->SetUri("/instances/" + instance + "/metadata/SopClassUid"); + command->AcquirePayload( + new OrthancSopClassHandler( + GetLoader(), GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid(), instance)); + GetLoader()->Schedule(command.release()); + } + } + } + }; + + + void SeriesThumbnailsLoader::Schedule(IOracleCommand* command) + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority_, command); + } + + + void SeriesThumbnailsLoader::Handle(const HttpCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + dynamic_cast(message.GetOrigin().GetPayload()).HandleSuccess(message.GetAnswer(), message.GetAnswerHeaders()); + } + + + void SeriesThumbnailsLoader::Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + dynamic_cast(message.GetOrigin().GetPayload()).HandleSuccess(message.GetAnswer(), message.GetAnswerHeaders()); + } + + + void SeriesThumbnailsLoader::Handle(const GetOrthancImageCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + + std::unique_ptr resized(Orthanc::ImageProcessing::FitSize(message.GetImage(), width_, height_)); + + std::string jpeg; + Orthanc::JpegWriter writer; + writer.SetQuality(JPEG_QUALITY); + writer.WriteToMemory(jpeg, *resized); + + const ThumbnailInformation& info = dynamic_cast(message.GetOrigin().GetPayload()); + AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(), + info.GetSeriesInstanceUid(), new Thumbnail(jpeg, Orthanc::MIME_JPEG)); + } + + + void SeriesThumbnailsLoader::Handle(const OracleCommandExceptionMessage& message) + { + const OracleCommandBase& command = dynamic_cast(message.GetOrigin()); + assert(command.HasPayload()); + + if (command.GetType() == IOracleCommand::Type_GetOrthancImage) + { + // This is presumably a HTTP status 301 (Moved permanently) + // because of an unsupported DICOM file in "/preview" + const ThumbnailInformation& info = dynamic_cast(command.GetPayload()); + AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(), + info.GetSeriesInstanceUid(), new Thumbnail(SeriesThumbnailType_Unsupported)); + } + else + { + dynamic_cast(command.GetPayload()).HandleError(); + } + } + + + SeriesThumbnailsLoader::SeriesThumbnailsLoader(ILoadersContext& context, + int priority) : + context_(context), + priority_(priority), + width_(128), + height_(128) + { + } + + + boost::shared_ptr SeriesThumbnailsLoader::Factory::Create(ILoadersContext::ILock& stone) + { + boost::shared_ptr result(new SeriesThumbnailsLoader(stone.GetContext(), priority_)); + result->Register(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); + result->Register(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); + result->Register(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); + result->Register(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); + return result; + } + + + void SeriesThumbnailsLoader::SetThumbnailSize(unsigned int width, + unsigned int height) + { + if (width <= 0 || + height <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + width_ = width; + height_ = height; + } + } + + + void SeriesThumbnailsLoader::Clear() + { + for (Thumbnails::iterator it = thumbnails_.begin(); it != thumbnails_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + + thumbnails_.clear(); + } + + + SeriesThumbnailType SeriesThumbnailsLoader::GetSeriesThumbnail(std::string& image, + std::string& mime, + const std::string& seriesInstanceUid) const + { + Thumbnails::const_iterator found = thumbnails_.find(seriesInstanceUid); + + if (found == thumbnails_.end()) + { + return SeriesThumbnailType_NotLoaded; + } + else + { + assert(found->second != NULL); + image.assign(found->second->GetImage()); + mime.assign(found->second->GetMime()); + return found->second->GetType(); + } + } + + + void SeriesThumbnailsLoader::ScheduleLoadThumbnail(const DicomSource& source, + const std::string& patientId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) + { + if (source.IsDicomWeb()) + { + if (!source.HasDicomWebRendered()) + { + // TODO - Could use DCMTK here + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "DICOMweb server is not able to generate renderings of DICOM series"); + } + + const std::string uri = ("/studies/" + studyInstanceUid + + "/series/" + seriesInstanceUid + "/rendered"); + + std::map arguments, headers; + arguments["viewport"] = (boost::lexical_cast(width_) + "," + + boost::lexical_cast(height_)); + + // Needed to set this header explicitly, as long as emscripten + // does not include macro "EMSCRIPTEN_FETCH_RESPONSE_HEADERS" + // https://github.com/emscripten-core/emscripten/pull/8486 + headers["Accept"] = Orthanc::MIME_JPEG; + + std::unique_ptr command( + source.CreateDicomWebCommand( + uri, arguments, headers, new DicomWebThumbnailHandler( + shared_from_this(), source, studyInstanceUid, seriesInstanceUid))); + Schedule(command.release()); + } + else if (source.IsOrthanc()) + { + // Dummy SOP Instance UID, as we are working at the "series" level + Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, "dummy"); + + std::unique_ptr command(new OrthancRestApiCommand); + command->SetUri("/series/" + hasher.HashSeries()); + command->AcquirePayload(new SelectOrthancInstanceHandler( + shared_from_this(), source, studyInstanceUid, seriesInstanceUid)); + Schedule(command.release()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "Can only load thumbnails from Orthanc or DICOMweb"); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/SeriesThumbnailsLoader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/SeriesThumbnailsLoader.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,210 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Oracle/GetOrthancImageCommand.h" +#include "../Oracle/HttpCommand.h" +#include "../Oracle/OracleCommandExceptionMessage.h" +#include "../Oracle/OrthancRestApiCommand.h" +#include "DicomSource.h" +#include "ILoaderFactory.h" +#include "OracleScheduler.h" + + +namespace OrthancStone +{ + enum SeriesThumbnailType + { + SeriesThumbnailType_NotLoaded = 1, // The remote server cannot decode this image + SeriesThumbnailType_Unsupported = 2, // The remote server cannot decode this image + SeriesThumbnailType_Pdf = 3, + SeriesThumbnailType_Video = 4, + SeriesThumbnailType_Image = 5 + }; + + + class SeriesThumbnailsLoader : + public IObservable, + public ObserverBase + { + private: + class Thumbnail : public boost::noncopyable + { + private: + SeriesThumbnailType type_; + std::string image_; + std::string mime_; + + public: + Thumbnail(const std::string& image, + const std::string& mime); + + Thumbnail(SeriesThumbnailType type); + + SeriesThumbnailType GetType() const + { + return type_; + } + + const std::string& GetImage() const + { + return image_; + } + + const std::string& GetMime() const + { + return mime_; + } + }; + + public: + class ThumbnailLoadedMessage : public OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const DicomSource& source_; + const std::string& studyInstanceUid_; + const std::string& seriesInstanceUid_; + const Thumbnail& thumbnail_; + + public: + ThumbnailLoadedMessage(const SeriesThumbnailsLoader& origin, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const Thumbnail& thumbnail) : + OriginMessage(origin), + source_(source), + studyInstanceUid_(studyInstanceUid), + seriesInstanceUid_(seriesInstanceUid), + thumbnail_(thumbnail) + { + } + + const DicomSource& GetDicomSource() const + { + return source_; + } + + SeriesThumbnailType GetType() const + { + return thumbnail_.GetType(); + } + + const std::string& GetStudyInstanceUid() const + { + return studyInstanceUid_; + } + + const std::string& GetSeriesInstanceUid() const + { + return seriesInstanceUid_; + } + + const std::string& GetEncodedImage() const + { + return thumbnail_.GetImage(); + } + + const std::string& GetMime() const + { + return thumbnail_.GetMime(); + } + }; + + private: + class Handler; + class DicomWebSopClassHandler; + class DicomWebThumbnailHandler; + class ThumbnailInformation; + class OrthancSopClassHandler; + class SelectOrthancInstanceHandler; + + // Maps a "Series Instance UID" to a thumbnail + typedef std::map Thumbnails; + + ILoadersContext& context_; + Thumbnails thumbnails_; + int priority_; + unsigned int width_; + unsigned int height_; + + void AcquireThumbnail(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + Thumbnail* thumbnail /* takes ownership */); + + void Schedule(IOracleCommand* command); + + void Handle(const HttpCommand::SuccessMessage& message); + + void Handle(const OrthancRestApiCommand::SuccessMessage& message); + + void Handle(const GetOrthancImageCommand::SuccessMessage& message); + + void Handle(const OracleCommandExceptionMessage& message); + + SeriesThumbnailsLoader(ILoadersContext& context, + int priority); + + public: + class Factory : public ILoaderFactory + { + private: + int priority_; + + public: + Factory() : + priority_(0) + { + } + + void SetPriority(int priority) + { + priority_ = priority; + } + + virtual boost::shared_ptr Create(ILoadersContext::ILock& context); + }; + + + virtual ~SeriesThumbnailsLoader() + { + Clear(); + } + + void SetThumbnailSize(unsigned int width, + unsigned int height); + + void Clear(); + + SeriesThumbnailType GetSeriesThumbnail(std::string& image, + std::string& mime, + const std::string& seriesInstanceUid) const; + + void ScheduleLoadThumbnail(const DicomSource& source, + const std::string& patientId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/WebAssemblyLoadersContext.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/WebAssemblyLoadersContext.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,96 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "WebAssemblyLoadersContext.h" + +namespace OrthancStone +{ + class WebAssemblyLoadersContext::Locker : public ILoadersContext::ILock + { + private: + WebAssemblyLoadersContext& that_; + + public: + Locker(WebAssemblyLoadersContext& that) : + that_(that) + { + } + + virtual ILoadersContext& GetContext() const ORTHANC_OVERRIDE + { + return that_; + } + + virtual IObservable& GetOracleObservable() const ORTHANC_OVERRIDE + { + return that_.oracle_.GetOracleObservable(); + } + + virtual void Schedule(boost::shared_ptr receiver, + int priority, + IOracleCommand* command /* Takes ownership */) ORTHANC_OVERRIDE + { + that_.scheduler_->Schedule(receiver, priority, command); + } + + virtual void CancelRequests(boost::shared_ptr receiver) ORTHANC_OVERRIDE + { + that_.scheduler_->CancelRequests(receiver); + } + + virtual void CancelAllRequests() ORTHANC_OVERRIDE + { + that_.scheduler_->CancelAllRequests(); + } + + virtual void AddLoader(boost::shared_ptr loader) ORTHANC_OVERRIDE + { + that_.loaders_.push_back(loader); + } + + virtual void GetStatistics(uint64_t& scheduledCommands, + uint64_t& processedCommands) ORTHANC_OVERRIDE + { + scheduledCommands = that_.scheduler_->GetTotalScheduled(); + processedCommands = that_.scheduler_->GetTotalProcessed(); + } + }; + + + WebAssemblyLoadersContext::WebAssemblyLoadersContext(unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority) + { + scheduler_ = OracleScheduler::Create(oracle_, oracle_.GetOracleObservable(), oracle_, + maxHighPriority, maxStandardPriority, maxLowPriority); + + if (!scheduler_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + ILoadersContext::ILock* WebAssemblyLoadersContext::Lock() + { + return new Locker(*this); + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Loaders/WebAssemblyLoadersContext.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Loaders/WebAssemblyLoadersContext.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ILoadersContext.h" +#include "../Oracle/WebAssemblyOracle.h" +#include "OracleScheduler.h" + +#include + +namespace OrthancStone +{ + class WebAssemblyLoadersContext : public ILoadersContext + { + private: + class Locker; + + WebAssemblyOracle oracle_; + boost::shared_ptr scheduler_; + std::list< boost::shared_ptr > loaders_; + + public: + WebAssemblyLoadersContext(unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority); + + void SetLocalOrthanc(const std::string& root) + { + oracle_.SetLocalOrthanc(root); + } + + void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc) + { + oracle_.SetRemoteOrthanc(orthanc); + } + + void SetDicomCacheSize(size_t size) + { + oracle_.SetDicomCacheSize(size); + } + + virtual ILock* Lock() ORTHANC_OVERRIDE; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/ICallable.h --- a/Framework/Messages/ICallable.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Messages/ICallable.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,23 +22,21 @@ #pragma once #include "IMessage.h" +#include "IObserver.h" #include #include +#include #include #include -#include - -namespace OrthancStone { - - class IObserver; - +namespace OrthancStone +{ // This is referencing an object and member function that can be notified // by an IObservable. The object must derive from IO - // The member functions must be of type "void Function(const IMessage& message)" or reference a derived class of IMessage + // The member functions must be of type "void Method(const IMessage& message)" or reference a derived class of IMessage class ICallable : public boost::noncopyable { public: @@ -50,61 +48,38 @@ virtual const MessageIdentifier& GetMessageIdentifier() = 0; - virtual IObserver* GetObserver() const = 0; - }; - - template - class MessageHandler: public ICallable - { + // TODO - Is this needed? + virtual boost::weak_ptr GetObserver() const = 0; }; template - class Callable : public MessageHandler + class Callable : public ICallable { private: - typedef void (TObserver::* MemberFunction) (const TMessage&); + typedef void (TObserver::* MemberMethod) (const TMessage&); - TObserver& observer_; - MemberFunction function_; - int64_t observerFingerprint_; + boost::weak_ptr observer_; + MemberMethod function_; public: - Callable(TObserver& observer, - MemberFunction function) : + Callable(boost::shared_ptr observer, + MemberMethod function) : observer_(observer), - function_(function), - observerFingerprint_(observer.GetFingerprint()) - { - } - - void ApplyInternal(const TMessage& message) + function_(function) { - int64_t currentFingerprint(observer_.GetFingerprint()); - if (observerFingerprint_ != currentFingerprint) - { - LOG(TRACE) << "The observer at address " << - std::hex << &observer_ << std::dec << - ") has a different fingerprint than the one recorded at callback " << - "registration time. This means that it is not the same object as " << - "the one recorded, even though their addresses are the same. " << - "Callback will NOT be sent!"; - LOG(TRACE) << " recorded fingerprint = " << observerFingerprint_ << - " current fingerprint = " << currentFingerprint; - } - else - { - LOG(TRACE) << "The recorded fingerprint is " << observerFingerprint_ - << " and the current fingerprint is " << currentFingerprint - << " -- callable will be called."; - (observer_.*function_) (message); - } } virtual void Apply(const IMessage& message) { - ApplyInternal(dynamic_cast(message)); + boost::shared_ptr lock(observer_); + if (lock) + { + TObserver& observer = dynamic_cast(*lock); + const TMessage& typedMessage = dynamic_cast(message); + (observer.*function_) (typedMessage); + } } virtual const MessageIdentifier& GetMessageIdentifier() @@ -112,41 +87,9 @@ return TMessage::GetStaticIdentifier(); } - virtual IObserver* GetObserver() const + virtual boost::weak_ptr GetObserver() const { - return &observer_; + return observer_; } }; - -#if 0 /* __cplusplus >= 201103L*/ - -#include - - template - class LambdaCallable : public MessageHandler - { - private: - - IObserver& observer_; - std::function lambda_; - - public: - LambdaCallable(IObserver& observer, - std::function lambdaFunction) : - observer_(observer), - lambda_(lambdaFunction) - { - } - - virtual void Apply(const IMessage& message) - { - lambda_(dynamic_cast(message)); - } - - virtual IObserver* GetObserver() const - { - return &observer_; - } - }; -#endif //__cplusplus >= 201103L } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/IMessage.h --- a/Framework/Messages/IMessage.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Messages/IMessage.h Wed Apr 22 14:05:47 2020 +0200 @@ -21,6 +21,7 @@ #pragma once +#include #include #include @@ -33,6 +34,12 @@ const char* file_; int line_; + bool IsEqual(const MessageIdentifier& other) const + { + return (line_ == other.line_ && + strcmp(file_, other.file_) == 0); + } + public: MessageIdentifier(const char* file, int line) : @@ -47,6 +54,11 @@ { } + std::string AsString() const + { + return std::string(file_) + ":" + boost::lexical_cast(line_); + } + bool operator< (const MessageIdentifier& other) const { if (file_ == NULL) @@ -62,6 +74,16 @@ return strcmp(file_, other.file_) < 0; } } + + bool operator== (const MessageIdentifier& other) const + { + return IsEqual(other); + } + + bool operator!= (const MessageIdentifier& other) const + { + return !IsEqual(other); + } }; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/IMessageEmitter.h --- a/Framework/Messages/IMessageEmitter.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Messages/IMessageEmitter.h Wed Apr 22 14:05:47 2020 +0200 @@ -24,6 +24,8 @@ #include "IObserver.h" #include "IMessage.h" +#include + namespace OrthancStone { /** @@ -39,7 +41,7 @@ { } - virtual void EmitMessage(const IObserver& observer, + virtual void EmitMessage(boost::weak_ptr observer, const IMessage& message) = 0; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/IObservable.cpp --- a/Framework/Messages/IObservable.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Messages/IObservable.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,8 +21,9 @@ #include "IObservable.h" +#include "../StoneException.h" + #include -#include #include @@ -40,20 +41,10 @@ delete *it2; } } - - // unregister the forwarders but don't delete them (they'll be - // deleted by the observable they are observing as any other - // callable) - for (Forwarders::iterator it = forwarders_.begin(); - it != forwarders_.end(); ++it) - { - IMessageForwarder* fw = *it; - broker_.Unregister(dynamic_cast(*fw)); - } } - void IObservable::RegisterObserverCallback(ICallable* callable) + void IObservable::RegisterCallable(ICallable* callable) { if (callable == NULL) { @@ -64,35 +55,10 @@ callables_[id].insert(callable); } - void IObservable::Unregister(IObserver *observer) - { - LOG(TRACE) << "IObservable::Unregister for IObserver at addr: " - << std::hex << observer << std::dec; - // delete all callables from this observer - for (Callables::iterator itCallableSet = callables_.begin(); - itCallableSet != callables_.end(); ++itCallableSet) - { - for (std::set::const_iterator - itCallable = itCallableSet->second.begin(); itCallable != itCallableSet->second.end(); ) - { - if ((*itCallable)->GetObserver() == observer) - { - LOG(TRACE) << " ** IObservable::Unregister : deleting callable: " - << std::hex << (*itCallable) << std::dec; - delete *itCallable; - itCallableSet->second.erase(itCallable++); - } - else - ++itCallable; - } - } - } - void IObservable::EmitMessageInternal(const IObserver* receiver, const IMessage& message) { - LOG(TRACE) << "IObservable::EmitMessageInternal receiver = " - << std::hex << receiver << std::dec; + //LOG(TRACE) << "IObservable::EmitMessageInternal receiver = " << std::hex << receiver << std::dec; Callables::const_iterator found = callables_.find(message.GetIdentifier()); if (found != callables_.end()) @@ -102,15 +68,36 @@ { assert(*it != NULL); - const IObserver* observer = (*it)->GetObserver(); - if (broker_.IsActive(*observer)) + boost::shared_ptr observer((*it)->GetObserver().lock()); + + if (observer) { if (receiver == NULL || // Are we broadcasting? - observer == receiver) // Not broadcasting, but this is the receiver + observer.get() == receiver) // Not broadcasting, but this is the receiver { - (*it)->Apply(message); + try + { + (*it)->Apply(message); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception on callable: " << e.What(); + } + catch (StoneException& e) + { + LOG(ERROR) << "Exception on callable: " << e.What(); + } + catch (...) + { + LOG(ERROR) << "Native exception on callable"; + } } } + else + { + // TODO => Remove "it" from the list of callables => This + // allows to suppress the need for "Unregister()" + } } } } @@ -122,21 +109,15 @@ } - void IObservable::EmitMessage(const IObserver& observer, + void IObservable::EmitMessage(boost::weak_ptr observer, const IMessage& message) { - LOG(TRACE) << "IObservable::EmitMessage observer = " - << std::hex << &observer << std::dec; - EmitMessageInternal(&observer, message); - } - - void IObservable::RegisterForwarder(IMessageForwarder* forwarder) - { - if (forwarder == NULL) + //LOG(TRACE) << "IObservable::EmitMessage observer = " << std::hex << &observer << std::dec; + + boost::shared_ptr lock(observer.lock()); + if (lock) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + EmitMessageInternal(lock.get(), message); } - - forwarders_.insert(forwarder); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/IObservable.h --- a/Framework/Messages/IObservable.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Messages/IObservable.h Wed Apr 22 14:05:47 2020 +0200 @@ -24,8 +24,6 @@ #include "../StoneEnumerations.h" #include "ICallable.h" #include "IObserver.h" -#include "MessageBroker.h" -#include "MessageForwarder.h" #include #include @@ -37,39 +35,20 @@ private: typedef std::map > Callables; - typedef std::set Forwarders; - - MessageBroker& broker_; Callables callables_; - Forwarders forwarders_; void EmitMessageInternal(const IObserver* receiver, const IMessage& message); public: - IObservable(MessageBroker& broker) : - broker_(broker) - { - } - virtual ~IObservable(); - MessageBroker& GetBroker() const - { - return broker_; - } - - // Takes ownsership - void RegisterObserverCallback(ICallable* callable); - - void Unregister(IObserver* observer); + // Takes ownership of the callable + void RegisterCallable(ICallable* callable); void BroadcastMessage(const IMessage& message); - void EmitMessage(const IObserver& observer, + void EmitMessage(boost::weak_ptr observer, const IMessage& message); - - // Takes ownsership - void RegisterForwarder(IMessageForwarder* forwarder); }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/IObserver.cpp --- a/Framework/Messages/IObserver.cpp Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#include "IObserver.h" - -#include "IMessage.h" -#include "../StoneException.h" - -#include -#include - -namespace OrthancStone -{ - static const uint64_t IObserver_FIRST_UNIQUE_ID = 10973; - static uint64_t IObserver_nextUniqueId = IObserver_FIRST_UNIQUE_ID; - - IObserver::IObserver(MessageBroker& broker) - : broker_(broker) - { - AssignFingerprint(); - broker_.Register(*this); - } - - IObserver::~IObserver() - { - try - { - LOG(TRACE) << "IObserver(" << std::hex << this << std::dec << ")::~IObserver : fingerprint_[0] == " << fingerprint_[0]; - fingerprint_[0] = 0xdeadbeef; - fingerprint_[1] = 0xdeadbeef; - fingerprint_[2] = 0xdeadbeef; - broker_.Unregister(*this); - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in ~IObserver: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in ~IObserver: " << e.What(); - } - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in ~IObserver: " << e.what(); - } - catch (...) - { - LOG(ERROR) << "Unknown exception in ~IObserver"; - } - } - - static const int64_t IObserver_UNIQUE_ID_MAGIC_NUMBER = 2742024; - - void IObserver::AssignFingerprint() - { - fingerprint_[0] = IObserver_nextUniqueId; - fingerprint_[1] = fingerprint_[0] / 2; - fingerprint_[2] = fingerprint_[1] + IObserver_UNIQUE_ID_MAGIC_NUMBER; - IObserver_nextUniqueId++; - } - - bool IObserver::DoesFingerprintLookGood() const - { - bool ok = (fingerprint_[0] >= IObserver_FIRST_UNIQUE_ID) && - (fingerprint_[1] == fingerprint_[0] / 2) && - (fingerprint_[2] == fingerprint_[1] + IObserver_UNIQUE_ID_MAGIC_NUMBER); - if(!ok) - { - LOG(INFO) << "Fingerprint not valid: " << " fingerprint_[0] = " << fingerprint_[0] << " fingerprint_[1] = " << fingerprint_[1]<< " fingerprint_[2] = " << fingerprint_[2]; - } - return ok; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/IObserver.h --- a/Framework/Messages/IObserver.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Messages/IObserver.h Wed Apr 22 14:05:47 2020 +0200 @@ -21,7 +21,7 @@ #pragma once -#include "MessageBroker.h" +#include #include @@ -29,29 +29,13 @@ { class IObserver : public boost::noncopyable { - private: - MessageBroker& broker_; - // the following is a int64_t with some checks that is used to - // disambiguate different observers that may have the same address - int64_t fingerprint_[3]; - - void AssignFingerprint(); - public: - IObserver(MessageBroker& broker); - - virtual ~IObserver(); - - int64_t GetFingerprint() const + IObserver() { - return fingerprint_[0]; } - bool DoesFingerprintLookGood() const; - - MessageBroker& GetBroker() const + virtual ~IObserver() { - return broker_; } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/LockingEmitter.h --- a/Framework/Messages/LockingEmitter.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#include -#include - -#include "IMessageEmitter.h" -#include "IObservable.h" - -#include - -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 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 lock_; - - public: - ReaderLock(LockingEmitter& that) : - that_(that), - lock_(that.mutex_) - { - } - }; - - - class WriterLock : public boost::noncopyable - { - private: - LockingEmitter& that_; - boost::unique_lock lock_; - - public: - WriterLock(LockingEmitter& that) : - that_(that), - lock_(that.mutex_) - { - } - - MessageBroker& GetBroker() - { - return that_.broker_; - } - - IObservable& GetOracleObservable() - { - return that_.oracleObservable_; - } - }; - }; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/MessageBroker.h --- a/Framework/Messages/MessageBroker.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#include "boost/noncopyable.hpp" - -#include - -namespace OrthancStone -{ - class IObserver; - - /* - * This is a central message broker. It keeps track of all observers and knows - * when an observer is deleted. - * This way, it can prevent an observable to send a message to a deleted observer. - */ - class MessageBroker : public boost::noncopyable - { - private: - std::set activeObservers_; // the list of observers that are currently alive (that have not been deleted) - - public: - MessageBroker() - { - } - - void Register(const IObserver& observer) - { - activeObservers_.insert(&observer); - } - - void Unregister(const IObserver& observer) - { - activeObservers_.erase(&observer); - } - - bool IsActive(const IObserver& observer) - { - return activeObservers_.find(&observer) != activeObservers_.end(); - } - }; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/MessageForwarder.cpp --- a/Framework/Messages/MessageForwarder.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#include "MessageForwarder.h" - -#include "IObservable.h" - -namespace OrthancStone -{ - - void IMessageForwarder::ForwardMessageInternal(const IMessage& message) - { - emitter_.BroadcastMessage(message); - } - - void IMessageForwarder::RegisterForwarderInEmitter() - { - emitter_.RegisterForwarder(this); - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/MessageForwarder.h --- a/Framework/Messages/MessageForwarder.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ICallable.h" -#include "IObserver.h" - -#include - -namespace OrthancStone -{ - - class IObservable; - - class IMessageForwarder : public IObserver - { - IObservable& emitter_; - public: - IMessageForwarder(MessageBroker& broker, IObservable& emitter) - : IObserver(broker), - emitter_(emitter) - {} - virtual ~IMessageForwarder() {} - - protected: - void ForwardMessageInternal(const IMessage& message); - void RegisterForwarderInEmitter(); - - }; - - /* When an Observer (B) simply needs to re-emit a message it has received, instead of implementing - * a specific member function to forward the message, it can create a MessageForwarder. - * The MessageForwarder will re-emit the message "in the name of (B)" - * - * Consider the chain where - * A is an observable - * | - * B is an observer of A and observable - * | - * C is an observer of B and knows that B is re-emitting many messages from A - * - * instead of implementing a callback, B will create a MessageForwarder that will emit the messages in his name: - * A.RegisterObserverCallback(new MessageForwarder(broker, *this) // where "this" is B - * - * in C: - * B.RegisterObserverCallback(new Callable(*this, &B::MyCallback)) // where "this" is C - */ - template - class MessageForwarder : public IMessageForwarder, public Callable, TMessage> - { - public: - MessageForwarder(MessageBroker& broker, - IObservable& emitter // the object that will emit the messages to forward - ) - : IMessageForwarder(broker, emitter), - Callable, TMessage>(*this, &MessageForwarder::ForwardMessage) - { - RegisterForwarderInEmitter(); - } - -protected: - void ForwardMessage(const TMessage& message) - { - ForwardMessageInternal(message); - } - - }; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Messages/ObserverBase.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/ObserverBase.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ICallable.h" +#include "IObserver.h" +#include "IObservable.h" + +#include + +#include + +namespace OrthancStone +{ + template + class ObserverBase : + public IObserver, + public boost::enable_shared_from_this + { + public: + boost::shared_ptr GetSharedObserver() + { + try + { + return this->shared_from_this(); + } + catch (boost::bad_weak_ptr&) + { + throw Orthanc::OrthancException( + Orthanc::ErrorCode_InternalError, + "Cannot get a shared pointer to an observer from its constructor, " + "or the observer is not created as a shared pointer"); + } + } + + template + ICallable* CreateCallable(void (TObserver::* MemberMethod) (const TMessage&)) + { + return new Callable(GetSharedObserver(), MemberMethod); + } + + template + void Register(IObservable& observable, + void (TObserver::* MemberMethod) (const TMessage&)) + { + observable.RegisterCallable(CreateCallable(MemberMethod)); + } + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/OpenGL/OpenGLIncludes.h --- a/Framework/OpenGL/OpenGLIncludes.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/OpenGL/OpenGLIncludes.h Wed Apr 22 14:05:47 2020 +0200 @@ -32,6 +32,8 @@ #if defined(__APPLE__) # include # include +#elif defined(QT_VERSION_MAJOR) && (QT_VERSION >= 5) +// Qt5 takes care of the inclusions #elif defined(_WIN32) // On Windows, use the compatibility headers provided by glew # include diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/OpenGL/SdlOpenGLContext.cpp --- a/Framework/OpenGL/SdlOpenGLContext.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/OpenGL/SdlOpenGLContext.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -68,6 +68,7 @@ GLenum err = glewInit(); if (GLEW_OK != err) { + LOG(ERROR) << glewGetErrorString(err); throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Cannot initialize glew"); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/OpenGL/SdlOpenGLContext.h --- a/Framework/OpenGL/SdlOpenGLContext.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/OpenGL/SdlOpenGLContext.h Wed Apr 22 14:05:47 2020 +0200 @@ -26,6 +26,8 @@ #include "IOpenGLContext.h" #include "../Viewport/SdlWindow.h" +#include + #include namespace OrthancStone @@ -62,6 +64,11 @@ virtual unsigned int GetCanvasWidth() const ORTHANC_OVERRIDE; virtual unsigned int GetCanvasHeight() const ORTHANC_OVERRIDE; + + void ToggleMaximize() + { + window_.ToggleMaximize(); + } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/OpenGL/WebAssemblyOpenGLContext.cpp --- a/Framework/OpenGL/WebAssemblyOpenGLContext.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/OpenGL/WebAssemblyOpenGLContext.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -37,26 +37,27 @@ class WebAssemblyOpenGLContext::PImpl { private: - std::string canvas_; + std::string canvasSelector_; EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_; unsigned int canvasWidth_; unsigned int canvasHeight_; bool isContextLost_; public: - PImpl(const std::string& canvas) - : canvas_(canvas) + PImpl(const std::string& canvasSelector) + : canvasSelector_(canvasSelector) , isContextLost_(false) { // Context configuration EmscriptenWebGLContextAttributes attr; emscripten_webgl_init_context_attributes(&attr); - context_ = emscripten_webgl_create_context(canvas.c_str(), &attr); + context_ = emscripten_webgl_create_context(canvasSelector.c_str(), &attr); if (context_ == 0) { - std::string message("Cannot create an OpenGL context for canvas: "); - message += canvas; + std::string message("Cannot create an OpenGL context for the element with the following CSS selector: \""); + message += canvasSelector; + message += "\" Please make sure the -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 flag has been passed to Emscripten when building."; LOG(ERROR) << message; throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, message); } @@ -113,9 +114,9 @@ } } - const std::string& GetCanvasIdentifier() const + const std::string& GetCanvasSelector() const { - return canvas_; + return canvasSelector_; } void MakeCurrent() @@ -128,7 +129,7 @@ if (emscripten_is_webgl_context_lost(context_)) { - LOG(ERROR) << "OpenGL context has been lost for canvas: " << canvas_; + LOG(ERROR) << "OpenGL context has been lost for canvas selector: " << canvasSelector_; SetLostContext(); throw StoneException(ErrorCode_WebGLContextLost); } @@ -166,7 +167,7 @@ void UpdateSize() { double w, h; - emscripten_get_element_css_size(canvas_.c_str(), &w, &h); + emscripten_get_element_css_size(canvasSelector_.c_str(), &w, &h); /** * Emscripten has the function emscripten_get_element_css_size() @@ -190,59 +191,16 @@ canvasHeight_ = static_cast(boost::math::iround(h)); } - emscripten_set_canvas_element_size(canvas_.c_str(), canvasWidth_, canvasHeight_); + emscripten_set_canvas_element_size(canvasSelector_.c_str(), canvasWidth_, canvasHeight_); } }; - WebAssemblyOpenGLContext::WebAssemblyOpenGLContext(const std::string& canvas) : - pimpl_(new PImpl(canvas)) + WebAssemblyOpenGLContext::WebAssemblyOpenGLContext(const std::string& canvasSelector) : + pimpl_(new PImpl(canvasSelector)) { } - //bool WebAssemblyOpenGLContext::TryRecreate() - //{ - // // LOG(ERROR) << "WebAssemblyOpenGLContext::TryRecreate() trying to recreate context"; - // try - // { - // std::string canvasId = GetCanvasIdentifier(); - // pimpl_.reset(new PImpl(canvasId)); - - // // no exception does not mean the context is fully - // // functional! Most probably, if we have >= than 16 - // // contexts, context wil remain lost for some time - // bool lost = IsContextLost(); - // if (lost) { - // // LOG(ERROR) << "WebAssemblyOpenGLContext::TryRecreate() context is still lost!"; - // return false; - // } else { - // return true; - // } - // } - // catch (const Orthanc::OrthancException& e) - // { - // if (e.HasDetails()) - // { - // LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::TryRecreate: " << e.What() << " Details: " << e.GetDetails(); - // } - // else - // { - // LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::TryRecreate: " << e.What(); - // } - // return false; - // } - // catch (const std::exception& e) - // { - // LOG(ERROR) << "std::exception in WebAssemblyOpenGLContext::TryRecreate: " << e.what(); - // return false; - // } - // catch (...) - // { - // LOG(ERROR) << "Unknown exception WebAssemblyOpenGLContext::in TryRecreate"; - // return false; - // } - //} - bool WebAssemblyOpenGLContext::IsContextLost() { return pimpl_->IsContextLost(); @@ -288,10 +246,10 @@ pimpl_->UpdateSize(); } - const std::string& WebAssemblyOpenGLContext::GetCanvasIdentifier() const + const std::string& WebAssemblyOpenGLContext::GetCanvasSelector() const { assert(pimpl_.get() != NULL); - return pimpl_->GetCanvasIdentifier(); + return pimpl_->GetCanvasSelector(); } } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/OpenGL/WebAssemblyOpenGLContext.h --- a/Framework/OpenGL/WebAssemblyOpenGLContext.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/OpenGL/WebAssemblyOpenGLContext.h Wed Apr 22 14:05:47 2020 +0200 @@ -54,7 +54,7 @@ boost::shared_ptr pimpl_; public: - WebAssemblyOpenGLContext(const std::string& canvas); + WebAssemblyOpenGLContext(const std::string& canvasSelector); virtual bool IsContextLost() ORTHANC_OVERRIDE; @@ -73,7 +73,7 @@ void UpdateSize(); - const std::string& GetCanvasIdentifier() const; + const std::string& GetCanvasSelector() const; /** diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/GenericOracleRunner.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GenericOracleRunner.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,521 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "GenericOracleRunner.h" + +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + +#include "GetOrthancImageCommand.h" +#include "GetOrthancWebViewerJpegCommand.h" +#include "HttpCommand.h" +#include "OracleCommandExceptionMessage.h" +#include "OrthancRestApiCommand.h" +#include "ParseDicomFromFileCommand.h" +#include "ParseDicomFromWadoCommand.h" +#include "ReadFileCommand.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "ParseDicomSuccessMessage.h" +# include +# include +static unsigned int BUCKET_DICOMDIR = 0; +static unsigned int BUCKET_SOP = 1; +#endif + +#include +#include +#include +#include +#include + +#include + + + +namespace OrthancStone +{ + 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 RunHttpCommand(std::string& answer, + Orthanc::HttpClient::HttpHeaders& answerHeaders, + const HttpCommand& command) + { + Orthanc::HttpClient client; + client.SetUrl(command.GetUrl()); + client.SetMethod(command.GetMethod()); + client.SetTimeout(command.GetTimeout()); + + CopyHttpHeaders(client, command.GetHttpHeaders()); + + if (command.HasCredentials()) + { + client.SetCredentials(command.GetUsername().c_str(), command.GetPassword().c_str()); + } + + if (command.GetMethod() == Orthanc::HttpMethod_Post || + command.GetMethod() == Orthanc::HttpMethod_Put) + { + client.SetBody(command.GetBody()); + } + + client.ApplyAndThrowException(answer, answerHeaders); + DecodeAnswer(answer, answerHeaders); + } + + + static void RunInternal(boost::weak_ptr receiver, + IMessageEmitter& emitter, + const HttpCommand& command) + { + std::string answer; + Orthanc::HttpClient::HttpHeaders answerHeaders; + RunHttpCommand(answer, answerHeaders, command); + + HttpCommand::SuccessMessage message(command, answerHeaders, answer); + emitter.EmitMessage(receiver, message); + } + + + static void RunOrthancRestApiCommand(std::string& answer, + Orthanc::HttpClient::HttpHeaders& answerHeaders, + const Orthanc::WebServiceParameters& orthanc, + const OrthancRestApiCommand& command) + { + Orthanc::HttpClient client(orthanc, command.GetUri()); + client.SetRedirectionFollowed(false); + 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()); + } + + client.ApplyAndThrowException(answer, answerHeaders); + DecodeAnswer(answer, answerHeaders); + } + + + static void RunInternal(boost::weak_ptr receiver, + IMessageEmitter& emitter, + const Orthanc::WebServiceParameters& orthanc, + const OrthancRestApiCommand& command) + { + std::string answer; + Orthanc::HttpClient::HttpHeaders answerHeaders; + RunOrthancRestApiCommand(answer, answerHeaders, orthanc, command); + + OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer); + emitter.EmitMessage(receiver, message); + } + + + static void RunInternal(boost::weak_ptr receiver, + IMessageEmitter& emitter, + const Orthanc::WebServiceParameters& orthanc, + const GetOrthancImageCommand& command) + { + Orthanc::HttpClient client(orthanc, command.GetUri()); + client.SetRedirectionFollowed(false); + client.SetTimeout(command.GetTimeout()); + + CopyHttpHeaders(client, command.GetHttpHeaders()); + + std::string answer; + Orthanc::HttpClient::HttpHeaders answerHeaders; + client.ApplyAndThrowException(answer, answerHeaders); + + DecodeAnswer(answer, answerHeaders); + + command.ProcessHttpAnswer(receiver, emitter, answer, answerHeaders); + } + + + static void RunInternal(boost::weak_ptr receiver, + IMessageEmitter& emitter, + const Orthanc::WebServiceParameters& orthanc, + const GetOrthancWebViewerJpegCommand& command) + { + Orthanc::HttpClient client(orthanc, command.GetUri()); + client.SetRedirectionFollowed(false); + client.SetTimeout(command.GetTimeout()); + + CopyHttpHeaders(client, command.GetHttpHeaders()); + + std::string answer; + Orthanc::HttpClient::HttpHeaders answerHeaders; + client.ApplyAndThrowException(answer, answerHeaders); + + DecodeAnswer(answer, answerHeaders); + + command.ProcessHttpAnswer(receiver, emitter, answer); + } + + + static std::string GetPath(const std::string& root, + const std::string& file) + { + boost::filesystem::path a(root); + boost::filesystem::path b(file); + + boost::filesystem::path c; + if (b.is_absolute()) + { + c = b; + } + else + { + c = a / b; + } + + return c.string(); + } + + + static void RunInternal(boost::weak_ptr receiver, + IMessageEmitter& emitter, + const std::string& root, + const ReadFileCommand& command) + { + std::string path = GetPath(root, command.GetPath()); + LOG(TRACE) << "Oracle reading file: " << path; + + std::string content; + Orthanc::SystemToolbox::ReadFile(content, path, true /* log */); + + ReadFileCommand::SuccessMessage message(command, content); + emitter.EmitMessage(receiver, message); + } + + +#if ORTHANC_ENABLE_DCMTK == 1 + static Orthanc::ParsedDicomFile* ParseDicom(uint64_t& fileSize, /* OUT */ + const std::string& path, + bool isPixelData) + { + if (!Orthanc::SystemToolbox::IsRegularFile(path)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile); + } + + LOG(TRACE) << "Parsing DICOM file, " << (isPixelData ? "with" : "without") + << " pixel data: " << path; + + boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); + + fileSize = Orthanc::SystemToolbox::GetFileSize(path); + + // Check for 32bit systems + if (fileSize != static_cast(static_cast(fileSize))) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); + } + + DcmFileFormat dicom; + bool ok; + + if (isPixelData) + { + ok = dicom.loadFile(path.c_str()).good(); + } + else + { +#if DCMTK_VERSION_NUMBER >= 362 + /** + * NB : We could stop at (0x3007, 0x0000) instead of + * DCM_PixelData as the Stone framework does not use further + * tags (cf. the Orthanc::DICOM_TAG_* constants), but we still + * use "PixelData" as this does not change the runtime much, and + * as it is more explicit. + **/ + static const DcmTagKey STOP = DCM_PixelData; + //static const DcmTagKey STOP(0x3007, 0x0000); + + ok = dicom.loadFileUntilTag(path.c_str(), EXS_Unknown, EGL_noChange, + DCM_MaxReadLength, ERM_autoDetect, STOP).good(); +#else + // The primitive "loadFileUntilTag" was introduced in DCMTK 3.6.2 + ok = dicom.loadFile(path.c_str()).good(); +#endif + } + + if (ok) + { + std::unique_ptr result(new Orthanc::ParsedDicomFile(dicom)); + + boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); + LOG(TRACE) << path << ": parsed in " << (end-start).total_milliseconds() << " ms"; + + return result.release(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Cannot parse file: " + path); + } + } + + + static void RunInternal(boost::weak_ptr receiver, + IMessageEmitter& emitter, + boost::shared_ptr cache, + const std::string& root, + const ParseDicomFromFileCommand& command) + { + const std::string path = GetPath(root, command.GetPath()); + + if (cache) + { + ParsedDicomCache::Reader reader(*cache, BUCKET_DICOMDIR, path); + if (reader.IsValid() && + (!command.IsPixelDataIncluded() || + reader.HasPixelData())) + { + // Reuse the DICOM file from the cache + ParseDicomSuccessMessage message(command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData()); + emitter.EmitMessage(receiver, message); + return; + } + } + + uint64_t fileSize; + std::unique_ptr parsed(ParseDicom(fileSize, path, command.IsPixelDataIncluded())); + + if (fileSize != static_cast(fileSize)) + { + // Cannot load such a large file on 32-bit architecture + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); + } + + { + ParseDicomSuccessMessage message + (command, *parsed, static_cast(fileSize), command.IsPixelDataIncluded()); + emitter.EmitMessage(receiver, message); + } + + if (cache) + { + // Store it into the cache for future use + + // Invalidate to overwrite DICOM instance that would already + // be stored without pixel data + cache->Invalidate(BUCKET_DICOMDIR, path); + + cache->Acquire(BUCKET_DICOMDIR, path, parsed.release(), + static_cast(fileSize), command.IsPixelDataIncluded()); + } + } + + + static void RunInternal(boost::weak_ptr receiver, + IMessageEmitter& emitter, + boost::shared_ptr cache, + const Orthanc::WebServiceParameters& orthanc, + const ParseDicomFromWadoCommand& command) + { + if (cache) + { + ParsedDicomCache::Reader reader(*cache, BUCKET_SOP, command.GetSopInstanceUid()); + if (reader.IsValid() && + reader.HasPixelData()) + { + // Reuse the DICOM file from the cache + ParseDicomSuccessMessage message(command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData()); + emitter.EmitMessage(receiver, message); + return; + } + } + + std::string answer; + Orthanc::HttpClient::HttpHeaders answerHeaders; + + switch (command.GetRestCommand().GetType()) + { + case IOracleCommand::Type_Http: + RunHttpCommand(answer, answerHeaders, dynamic_cast(command.GetRestCommand())); + break; + + case IOracleCommand::Type_OrthancRestApi: + RunOrthancRestApiCommand(answer, answerHeaders, orthanc, + dynamic_cast(command.GetRestCommand())); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + size_t fileSize; + std::unique_ptr parsed(ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, answerHeaders)); + + { + ParseDicomSuccessMessage message(command, *parsed, fileSize, + true /* pixel data always is included in WADO-RS */); + emitter.EmitMessage(receiver, message); + } + + if (cache) + { + // Store it into the cache for future use + cache->Acquire(BUCKET_SOP, command.GetSopInstanceUid(), parsed.release(), fileSize, true); + } + } +#endif + + + void GenericOracleRunner::Run(boost::weak_ptr receiver, + IMessageEmitter& emitter, + const IOracleCommand& command) + { + Orthanc::ErrorCode error = Orthanc::ErrorCode_Success; + + try + { + switch (command.GetType()) + { + case IOracleCommand::Type_Sleep: + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, + "Sleep command cannot be executed by the runner"); + + case IOracleCommand::Type_Http: + RunInternal(receiver, emitter, dynamic_cast(command)); + break; + + case IOracleCommand::Type_OrthancRestApi: + RunInternal(receiver, emitter, orthanc_, + dynamic_cast(command)); + break; + + case IOracleCommand::Type_GetOrthancImage: + RunInternal(receiver, emitter, orthanc_, + dynamic_cast(command)); + break; + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + RunInternal(receiver, emitter, orthanc_, + dynamic_cast(command)); + break; + + case IOracleCommand::Type_ReadFile: + RunInternal(receiver, emitter, rootDirectory_, + dynamic_cast(command)); + break; + + case IOracleCommand::Type_ParseDicomFromFile: + case IOracleCommand::Type_ParseDicomFromWado: +#if ORTHANC_ENABLE_DCMTK == 1 + switch (command.GetType()) + { + case IOracleCommand::Type_ParseDicomFromFile: + RunInternal(receiver, emitter, dicomCache_, rootDirectory_, + dynamic_cast(command)); + break; + + case IOracleCommand::Type_ParseDicomFromWado: + RunInternal(receiver, emitter, dicomCache_, orthanc_, + dynamic_cast(command)); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + break; +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "DCMTK must be enabled to parse DICOM files"); +#endif + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception within the oracle: " << e.What(); + error = e.GetErrorCode(); + } + catch (...) + { + LOG(ERROR) << "Threaded exception within the oracle"; + error = Orthanc::ErrorCode_InternalError; + } + + if (error != Orthanc::ErrorCode_Success) + { + OracleCommandExceptionMessage message(command, error); + emitter.EmitMessage(receiver, message); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/GenericOracleRunner.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GenericOracleRunner.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,87 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "../Toolbox/ParsedDicomCache.h" +#endif + +#include "IOracleCommand.h" +#include "../Messages/IMessageEmitter.h" + +#include // For ORTHANC_OVERRIDE +#include + +namespace OrthancStone +{ + class GenericOracleRunner : public boost::noncopyable + { + private: + Orthanc::WebServiceParameters orthanc_; + std::string rootDirectory_; + +#if ORTHANC_ENABLE_DCMTK == 1 + boost::shared_ptr dicomCache_; +#endif + + public: + GenericOracleRunner() : + rootDirectory_(".") + { + } + + void SetOrthanc(const Orthanc::WebServiceParameters& orthanc) + { + orthanc_ = orthanc; + } + + const Orthanc::WebServiceParameters& GetOrthanc() const + { + return orthanc_; + } + + void SetRootDirectory(const std::string& rootDirectory) + { + rootDirectory_ = rootDirectory; + } + + const std::string GetRootDirectory() const + { + return rootDirectory_; + } + +#if ORTHANC_ENABLE_DCMTK == 1 + void SetDicomCache(boost::shared_ptr cache) + { + dicomCache_ = cache; + } +#endif + + void Run(boost::weak_ptr receiver, + IMessageEmitter& emitter, + const IOracleCommand& command); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/GetOrthancImageCommand.cpp --- a/Framework/Oracle/GetOrthancImageCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/GetOrthancImageCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -29,20 +29,6 @@ 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_(600), @@ -58,35 +44,59 @@ } - void GetOrthancImageCommand::SetInstanceUri(const std::string& instance, - Orthanc::PixelFormat pixelFormat) + static std::string GetFormatSuffix(Orthanc::PixelFormat pixelFormat) { - uri_ = "/instances/" + instance; - switch (pixelFormat) { case Orthanc::PixelFormat_RGB24: - uri_ += "/preview"; - break; + return "preview"; case Orthanc::PixelFormat_Grayscale16: - uri_ += "/image-uint16"; - break; + return "image-uint16"; case Orthanc::PixelFormat_SignedGrayscale16: - uri_ += "/image-int16"; - break; + return "image-int16"; default: throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } } - void GetOrthancImageCommand::ProcessHttpAnswer(IMessageEmitter& emitter, - const IObserver& receiver, + + void GetOrthancImageCommand::SetInstanceUri(const std::string& instance, + Orthanc::PixelFormat pixelFormat) + { + uri_ = "/instances/" + instance + "/" + GetFormatSuffix(pixelFormat); + } + + + void GetOrthancImageCommand::SetFrameUri(const std::string& instance, + unsigned int frame, + Orthanc::PixelFormat pixelFormat) + { + uri_ = ("/instances/" + instance + "/frames/" + + boost::lexical_cast(frame) + "/" + GetFormatSuffix(pixelFormat)); + } + + + void GetOrthancImageCommand::ProcessHttpAnswer(boost::weak_ptr receiver, + IMessageEmitter& emitter, const std::string& answer, const HttpHeaders& answerHeaders) const { + for (HttpHeaders::const_iterator it = answerHeaders.begin(); it != answerHeaders.end(); ++it) + { + std::string key = Orthanc::Toolbox::StripSpaces(it->first); + Orthanc::Toolbox::ToLowerCase(key); + + if (key == "content-disposition" && + it->second == "filename=\"unsupported.png\"") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, + "Orthanc cannot decode this image"); + } + } + Orthanc::MimeType contentType = Orthanc::MimeType_Binary; for (HttpHeaders::const_iterator it = answerHeaders.begin(); @@ -115,7 +125,16 @@ case Orthanc::MimeType_Pam: { +#ifdef __EMSCRIPTEN__ + // "true" means we ask the PamReader to make an extra copy so that + // the resulting Orthanc::ImageAccessor is aligned (as malloc is). + // Indeed, even though alignment is not required in Web Assembly, + // Emscripten seems to check it and bail out if addresses are "odd" + image.reset(new Orthanc::PamReader(true)); +#else + // potentially unaligned, with is faster and consumes less heap memory image.reset(new Orthanc::PamReader); +#endif dynamic_cast(*image).ReadFromMemory(answer); break; } @@ -147,7 +166,24 @@ } } - SuccessMessage message(*this, image.release(), contentType); + //{ + // // DEBUG DISPLAY IMAGE PROPERTIES BGO 2020-04-11 + // const Orthanc::ImageAccessor& source = *image; + // const void* sourceBuffer = source.GetConstBuffer(); + // intptr_t sourceBufferInt = reinterpret_cast(sourceBuffer); + // int sourceWidth = source.GetWidth(); + // int sourceHeight = source.GetHeight(); + // int sourcePitch = source.GetPitch(); + + // // TODO: turn error into trace below + // LOG(ERROR) << "GetOrthancImageCommand::ProcessHttpAnswer | source:" + // << " W = " << sourceWidth << " H = " << sourceHeight + // << " P = " << sourcePitch << " B = " << sourceBufferInt + // << " B % 4 == " << sourceBufferInt % 4; + //} + + + SuccessMessage message(*this, *image, contentType); emitter.EmitMessage(receiver, message); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/GetOrthancImageCommand.h --- a/Framework/Oracle/GetOrthancImageCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/GetOrthancImageCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Messages/IMessageEmitter.h" -#include "OracleCommandWithPayload.h" +#include "OracleCommandBase.h" #include @@ -30,7 +30,7 @@ namespace OrthancStone { - class GetOrthancImageCommand : public OracleCommandWithPayload + class GetOrthancImageCommand : public OracleCommandBase { public: typedef std::map HttpHeaders; @@ -40,17 +40,22 @@ ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); private: - std::unique_ptr image_; - Orthanc::MimeType mime_; + const Orthanc::ImageAccessor& image_; + Orthanc::MimeType mime_; public: SuccessMessage(const GetOrthancImageCommand& command, - Orthanc::ImageAccessor* image, // Takes ownership - Orthanc::MimeType mime); + const Orthanc::ImageAccessor& image, + Orthanc::MimeType mime) : + OriginMessage(command), + image_(image), + mime_(mime) + { + } const Orthanc::ImageAccessor& GetImage() const { - return *image_; + return image_; } Orthanc::MimeType GetMimeType() const @@ -67,6 +72,15 @@ bool hasExpectedFormat_; Orthanc::PixelFormat expectedFormat_; + GetOrthancImageCommand(const GetOrthancImageCommand& other) : + uri_(other.uri_), + headers_(other.headers_), + timeout_(other.timeout_), + hasExpectedFormat_(other.hasExpectedFormat_), + expectedFormat_(other.expectedFormat_) + { + } + public: GetOrthancImageCommand(); @@ -75,6 +89,11 @@ return Type_GetOrthancImage; } + virtual IOracleCommand* Clone() const + { + return new GetOrthancImageCommand(*this); + } + void SetExpectedPixelFormat(Orthanc::PixelFormat format); void SetUri(const std::string& uri) @@ -85,6 +104,10 @@ void SetInstanceUri(const std::string& instance, Orthanc::PixelFormat pixelFormat); + void SetFrameUri(const std::string& instance, + unsigned int frame, + Orthanc::PixelFormat pixelFormat); + void SetHttpHeader(const std::string& key, const std::string& value) { @@ -111,8 +134,8 @@ return timeout_; } - void ProcessHttpAnswer(IMessageEmitter& emitter, - const IObserver& receiver, + void ProcessHttpAnswer(boost::weak_ptr receiver, + IMessageEmitter& emitter, const std::string& answer, const HttpHeaders& answerHeaders) const; }; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp --- a/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -29,23 +29,16 @@ #include #include +#ifdef _MSC_VER +// 'Json::Reader': Use CharReader and CharReaderBuilder instead +#pragma warning(disable:4996) +#endif + #include #include 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), @@ -76,8 +69,8 @@ } - void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(IMessageEmitter& emitter, - const IObserver& receiver, + void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(boost::weak_ptr receiver, + IMessageEmitter& emitter, const std::string& answer) const { // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()" @@ -149,7 +142,7 @@ } else { - SuccessMessage message(*this, reader.release()); + SuccessMessage message(*this, *reader); emitter.EmitMessage(receiver, message); return; } @@ -168,7 +161,7 @@ } else { - SuccessMessage message(*this, reader.release()); + SuccessMessage message(*this, *reader); emitter.EmitMessage(receiver, message); return; } @@ -210,8 +203,8 @@ float offset = static_cast(stretchLow) / scaling; Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); } - - SuccessMessage message(*this, image.release()); + + SuccessMessage message(*this, *image); emitter.EmitMessage(receiver, message); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/GetOrthancWebViewerJpegCommand.h --- a/Framework/Oracle/GetOrthancWebViewerJpegCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Messages/IMessageEmitter.h" -#include "OracleCommandWithPayload.h" +#include "OracleCommandBase.h" #include @@ -30,7 +30,7 @@ namespace OrthancStone { - class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload + class GetOrthancWebViewerJpegCommand : public OracleCommandBase { public: typedef std::map HttpHeaders; @@ -40,15 +40,19 @@ ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); private: - std::unique_ptr image_; + const Orthanc::ImageAccessor& image_; public: SuccessMessage(const GetOrthancWebViewerJpegCommand& command, - Orthanc::ImageAccessor* image); // Takes ownership + const Orthanc::ImageAccessor& image) : + OriginMessage(command), + image_(image) + { + } const Orthanc::ImageAccessor& GetImage() const { - return *image_; + return image_; } }; @@ -60,6 +64,16 @@ unsigned int timeout_; Orthanc::PixelFormat expectedFormat_; + GetOrthancWebViewerJpegCommand(const GetOrthancWebViewerJpegCommand& other) : + instanceId_(other.instanceId_), + frame_(other.frame_), + quality_(other.quality_), + headers_(other.headers_), + timeout_(other.timeout_), + expectedFormat_(other.expectedFormat_) + { + } + public: GetOrthancWebViewerJpegCommand(); @@ -68,6 +82,11 @@ return Type_GetOrthancWebViewerJpeg; } + virtual IOracleCommand* Clone() const + { + return new GetOrthancWebViewerJpegCommand(*this); + } + void SetExpectedPixelFormat(Orthanc::PixelFormat format) { expectedFormat_ = format; @@ -128,8 +147,8 @@ std::string GetUri() const; - void ProcessHttpAnswer(IMessageEmitter& emitter, - const IObserver& receiver, + void ProcessHttpAnswer(boost::weak_ptr receiver, + IMessageEmitter& emitter, const std::string& answer) const; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/HttpCommand.cpp --- a/Framework/Oracle/HttpCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/HttpCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -23,21 +23,16 @@ #include +#ifdef _MSC_VER +// 'Json::Reader': Use CharReader and CharReaderBuilder instead +#pragma warning(disable:4996) +#endif + #include #include namespace OrthancStone { - HttpCommand::SuccessMessage::SuccessMessage(const HttpCommand& command, - const HttpHeaders& answerHeaders, - std::string& answer) : - OriginMessage(command), - headers_(answerHeaders), - answer_(answer) - { - } - - void HttpCommand::SuccessMessage::ParseJsonBody(Json::Value& target) const { Json::Reader reader; @@ -76,4 +71,30 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } } + + + const std::string& HttpCommand::GetUsername() const + { + if (HasCredentials()) + { + return username_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + const std::string& HttpCommand::GetPassword() const + { + if (HasCredentials()) + { + return password_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/HttpCommand.h --- a/Framework/Oracle/HttpCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/HttpCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Messages/IMessage.h" -#include "OracleCommandWithPayload.h" +#include "OracleCommandBase.h" #include @@ -31,7 +31,7 @@ namespace OrthancStone { - class HttpCommand : public OracleCommandWithPayload + class HttpCommand : public OracleCommandBase { public: typedef std::map HttpHeaders; @@ -41,13 +41,18 @@ ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); private: - HttpHeaders headers_; - std::string answer_; + const HttpHeaders& headers_; + const std::string& answer_; public: SuccessMessage(const HttpCommand& command, const HttpHeaders& answerHeaders, - std::string& answer /* will be swapped to avoid a memcpy() */); + const std::string& answer) : + OriginMessage(command), + headers_(answerHeaders), + answer_(answer) + { + } const std::string& GetAnswer() const { @@ -56,7 +61,7 @@ void ParseJsonBody(Json::Value& target) const; - const HttpHeaders& GetAnswerHeaders() const + const HttpHeaders& GetAnswerHeaders() const { return headers_; } @@ -69,6 +74,19 @@ std::string body_; HttpHeaders headers_; unsigned int timeout_; + std::string username_; + std::string password_; + + HttpCommand(const HttpCommand& other) : + method_(other.method_), + url_(other.url_), + body_(other.body_), + headers_(other.headers_), + timeout_(other.timeout_), + username_(other.username_), + password_(other.password_) + { + } public: HttpCommand(); @@ -78,6 +96,11 @@ return Type_Http; } + virtual IOracleCommand* Clone() const + { + return new HttpCommand(*this); + } + void SetMethod(Orthanc::HttpMethod method) { method_ = method; @@ -137,5 +160,27 @@ { return timeout_; } + + void SetCredentials(const std::string& username, + const std::string& password) + { + username_ = username; + password_ = password; + } + + void ClearCredentials() + { + username_.clear(); + password_.clear(); + } + + bool HasCredentials() const + { + return !username_.empty(); + } + + const std::string& GetUsername() const; + + const std::string& GetPassword() const; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/IOracle.h --- a/Framework/Oracle/IOracle.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/IOracle.h Wed Apr 22 14:05:47 2020 +0200 @@ -24,6 +24,8 @@ #include "../Messages/IObserver.h" #include "IOracleCommand.h" +#include + namespace OrthancStone { class IOracle : public boost::noncopyable @@ -33,7 +35,12 @@ { } - virtual void Schedule(const IObserver& receiver, + /** + * Returns "true" iff the command has actually been queued. If + * "false" is returned, the command has been freed, and it won't + * be processed (this is the case if the oracle is stopped). + **/ + virtual bool Schedule(boost::shared_ptr receiver, IOracleCommand* command) = 0; // Takes ownership }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/IOracleCommand.h --- a/Framework/Oracle/IOracleCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/IOracleCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -21,7 +21,7 @@ #pragma once -#include +#include namespace OrthancStone { @@ -30,11 +30,14 @@ public: enum Type { + Type_GetOrthancImage, + Type_GetOrthancWebViewerJpeg, Type_Http, - Type_Sleep, Type_OrthancRestApi, - Type_GetOrthancImage, - Type_GetOrthancWebViewerJpeg + Type_ParseDicomFromFile, + Type_ParseDicomFromWado, + Type_ReadFile, + Type_Sleep }; virtual ~IOracleCommand() @@ -42,5 +45,8 @@ } virtual Type GetType() const = 0; + + // This only clones the command, *not* its possibly associated payload + virtual IOracleCommand* Clone() const = 0; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/OracleCommandBase.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OracleCommandBase.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OracleCommandBase.h" + +#include + +namespace OrthancStone +{ + void OracleCommandBase::AcquirePayload(Orthanc::IDynamicObject* payload) + { + if (payload == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + payload_.reset(payload); + } + } + + + Orthanc::IDynamicObject& OracleCommandBase::GetPayload() const + { + if (HasPayload()) + { + return *payload_; + } + else + { + LOG(ERROR) << "OracleCommandBase::GetPayload(): (!HasPayload())"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + Orthanc::IDynamicObject* OracleCommandBase::ReleasePayload() + { + if (HasPayload()) + { + return payload_.release(); + } + else + { + LOG(ERROR) << "OracleCommandBase::ReleasePayload(): (!HasPayload())"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/OracleCommandBase.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OracleCommandBase.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,50 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "IOracleCommand.h" + +#include +#include + +#include + +namespace OrthancStone +{ + class OracleCommandBase : public IOracleCommand + { + private: + std::unique_ptr payload_; + + public: + void AcquirePayload(Orthanc::IDynamicObject* payload); + + virtual bool HasPayload() const + { + return (payload_.get() != NULL); + } + + virtual Orthanc::IDynamicObject& GetPayload() const; + + Orthanc::IDynamicObject* ReleasePayload(); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/OracleCommandExceptionMessage.h --- a/Framework/Oracle/OracleCommandExceptionMessage.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/OracleCommandExceptionMessage.h Wed Apr 22 14:05:47 2020 +0200 @@ -28,34 +28,28 @@ namespace OrthancStone { - class OracleCommandExceptionMessage : public IMessage + class OracleCommandExceptionMessage : public OriginMessage { ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); private: - const IOracleCommand& command_; - Orthanc::OrthancException exception_; + Orthanc::OrthancException exception_; public: OracleCommandExceptionMessage(const IOracleCommand& command, - const Orthanc::OrthancException& exception) : - command_(command), - exception_(exception) + const Orthanc::ErrorCode& error) : + OriginMessage(command), + exception_(error) { } OracleCommandExceptionMessage(const IOracleCommand& command, - const Orthanc::ErrorCode& error) : - command_(command), - exception_(error) + const Orthanc::OrthancException& exception) : + OriginMessage(command), + exception_(exception) { } - const IOracleCommand& GetCommand() const - { - return command_; - } - const Orthanc::OrthancException& GetException() const { return exception_; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/OracleCommandWithPayload.cpp --- a/Framework/Oracle/OracleCommandWithPayload.cpp Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#include "OracleCommandWithPayload.h" - -#include - -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 - { - LOG(ERROR) << "OracleCommandWithPayload::GetPayload(): (!HasPayload())"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - - Orthanc::IDynamicObject* OracleCommandWithPayload::ReleasePayload() - { - if (HasPayload()) - { - return payload_.release(); - } - else - { - LOG(ERROR) << "OracleCommandWithPayload::ReleasePayload(): (!HasPayload())"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/OracleCommandWithPayload.h --- a/Framework/Oracle/OracleCommandWithPayload.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IOracleCommand.h" - -#include -#include - -#include - -namespace OrthancStone -{ - class OracleCommandWithPayload : public IOracleCommand - { - private: - std::unique_ptr payload_; - - public: - void SetPayload(Orthanc::IDynamicObject* payload); - - bool HasPayload() const - { - return (payload_.get() != NULL); - } - - Orthanc::IDynamicObject& GetPayload() const; - - Orthanc::IDynamicObject* ReleasePayload(); - }; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/OrthancRestApiCommand.cpp --- a/Framework/Oracle/OrthancRestApiCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/OrthancRestApiCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -23,21 +23,16 @@ #include +#ifdef _MSC_VER +// 'Json::Reader': Use CharReader and CharReaderBuilder instead +#pragma warning(disable:4996) +#endif + #include #include 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; @@ -51,7 +46,8 @@ OrthancRestApiCommand::OrthancRestApiCommand() : method_(Orthanc::HttpMethod_Get), uri_("/"), - timeout_(600) + timeout_(600), + applyPlugins_(false) { } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/OrthancRestApiCommand.h --- a/Framework/Oracle/OrthancRestApiCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/OrthancRestApiCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,7 +22,7 @@ #pragma once #include "../Messages/IMessage.h" -#include "OracleCommandWithPayload.h" +#include "OracleCommandBase.h" #include @@ -31,7 +31,7 @@ namespace OrthancStone { - class OrthancRestApiCommand : public OracleCommandWithPayload + class OrthancRestApiCommand : public OracleCommandBase { public: typedef std::map HttpHeaders; @@ -41,14 +41,19 @@ ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); private: - HttpHeaders headers_; - std::string answer_; + const HttpHeaders& headers_; + const std::string& answer_; public: SuccessMessage(const OrthancRestApiCommand& command, const HttpHeaders& answerHeaders, - std::string& answer /* will be swapped to avoid a memcpy() */); - + const std::string& answer) : + OriginMessage(command), + headers_(answerHeaders), + answer_(answer) + { + } + const std::string& GetAnswer() const { return answer_; @@ -69,7 +74,18 @@ std::string body_; HttpHeaders headers_; unsigned int timeout_; + bool applyPlugins_; // Only makes sense for Stone as an Orthanc plugin + OrthancRestApiCommand(const OrthancRestApiCommand& other) : + method_(other.method_), + uri_(other.uri_), + body_(other.body_), + headers_(other.headers_), + timeout_(other.timeout_), + applyPlugins_(other.applyPlugins_) + { + } + public: OrthancRestApiCommand(); @@ -78,6 +94,11 @@ return Type_OrthancRestApi; } + virtual IOracleCommand* Clone() const + { + return new OrthancRestApiCommand(*this); + } + void SetMethod(Orthanc::HttpMethod method) { method_ = method; @@ -137,5 +158,15 @@ { return timeout_; } + + void SetApplyPlugins(bool applyPlugins) + { + applyPlugins_ = applyPlugins; + } + + bool IsApplyPlugins() const + { + return applyPlugins_; + } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/ParseDicomFromFileCommand.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromFileCommand.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "ParseDicomFromFileCommand.h" + +#include + +#include + +namespace OrthancStone +{ + std::string ParseDicomFromFileCommand::GetDicomDirPath(const std::string& dicomDirPath, + const std::string& file) + { + std::string tmp = file; + +#if !defined(_WIN32) + std::replace(tmp.begin(), tmp.end(), '\\', '/'); +#endif + + boost::filesystem::path base = boost::filesystem::path(dicomDirPath).parent_path(); + + return (base / tmp).string(); + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/ParseDicomFromFileCommand.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromFileCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,84 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "OracleCommandBase.h" + +#include + +namespace OrthancStone +{ + class ParseDicomFromFileCommand : public OracleCommandBase + { + private: + std::string path_; + bool pixelDataIncluded_; + + ParseDicomFromFileCommand(const ParseDicomFromFileCommand& other) : + path_(other.path_), + pixelDataIncluded_(other.pixelDataIncluded_) + { + } + + public: + ParseDicomFromFileCommand(const std::string& path) : + path_(path), + pixelDataIncluded_(true) + { + } + + ParseDicomFromFileCommand(const std::string& dicomDirPath, + const std::string& file) : + path_(GetDicomDirPath(dicomDirPath, file)), + pixelDataIncluded_(true) + { + } + + static std::string GetDicomDirPath(const std::string& dicomDirPath, + const std::string& file); + + virtual Type GetType() const + { + return Type_ParseDicomFromFile; + } + + virtual IOracleCommand* Clone() const + { + return new ParseDicomFromFileCommand(*this); + } + + const std::string& GetPath() const + { + return path_; + } + + bool IsPixelDataIncluded() const + { + return pixelDataIncluded_; + } + + void SetPixelDataIncluded(bool included) + { + pixelDataIncluded_ = included; + } + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/ParseDicomFromWadoCommand.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromWadoCommand.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "ParseDicomFromWadoCommand.h" + +#include + +namespace OrthancStone +{ + ParseDicomFromWadoCommand::ParseDicomFromWadoCommand(const std::string& sopInstanceUid, + IOracleCommand* restCommand) : + sopInstanceUid_(sopInstanceUid), + restCommand_(restCommand) + { + if (restCommand == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (restCommand_->GetType() != Type_Http && + restCommand_->GetType() != Type_OrthancRestApi) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); + } + } + + + IOracleCommand* ParseDicomFromWadoCommand::Clone() const + { + assert(restCommand_.get() != NULL); + return new ParseDicomFromWadoCommand(sopInstanceUid_, restCommand_->Clone()); + } + + + const IOracleCommand& ParseDicomFromWadoCommand::GetRestCommand() const + { + assert(restCommand_.get() != NULL); + return *restCommand_; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/ParseDicomFromWadoCommand.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromWadoCommand.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "OracleCommandBase.h" + +#include + +namespace OrthancStone +{ + class ParseDicomFromWadoCommand : public OracleCommandBase + { + private: + std::string sopInstanceUid_; + std::unique_ptr restCommand_; + + public: + ParseDicomFromWadoCommand(const std::string& sopInstanceUid, + IOracleCommand* restCommand); + + virtual Type GetType() const + { + return Type_ParseDicomFromWado; + } + + virtual IOracleCommand* Clone() const; + + const std::string& GetSopInstanceUid() const + { + return sopInstanceUid_; + } + + const IOracleCommand& GetRestCommand() const; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/ParseDicomSuccessMessage.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomSuccessMessage.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,107 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "ParseDicomSuccessMessage.h" + +#include +#include +#include + +namespace OrthancStone +{ + class MultipartHandler : public Orthanc::MultipartStreamReader::IHandler + { + private: + std::unique_ptr dicom_; + size_t size_; + + public: + MultipartHandler() : + size_(0) + { + } + + virtual void HandlePart(const std::map& headers, + const void* part, + size_t size) + { + if (dicom_.get()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "Multiple DICOM instances were contained in a WADO-RS request"); + } + else + { + dicom_.reset(new Orthanc::ParsedDicomFile(part, size)); + size_ = size; + } + } + + Orthanc::ParsedDicomFile* ReleaseDicom() + { + if (dicom_.get()) + { + return dicom_.release(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "WADO-RS request didn't contain any DICOM instance"); + } + } + + size_t GetSize() const + { + return size_; + } + }; + + + Orthanc::ParsedDicomFile* ParseDicomSuccessMessage::ParseWadoAnswer( + size_t& fileSize /* OUT */, + const std::string& answer, + const std::map& headers) + { + std::string contentType, subType, boundary, header; + if (Orthanc::MultipartStreamReader::GetMainContentType(header, headers) && + Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, header) && + contentType == "multipart/related" && + subType == "application/dicom") + { + MultipartHandler handler; + + { + Orthanc::MultipartStreamReader reader(boundary); + reader.SetHandler(handler); + reader.AddChunk(answer); + reader.CloseStream(); + } + + fileSize = handler.GetSize(); + return handler.ReleaseDicom(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "Multipart/related answer of application/dicom was expected from DICOMweb server"); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/ParseDicomSuccessMessage.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomSuccessMessage.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,85 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK != 1 +# error Support for DCMTK must be enabled to use ParseDicomFromFileCommand +#endif + +#include "OracleCommandBase.h" +#include "../Messages/IMessageEmitter.h" +#include "../Messages/IObserver.h" + +#include + +namespace Orthanc +{ + class ParsedDicomFile; +} + +namespace OrthancStone +{ + class ParseDicomSuccessMessage : public OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + Orthanc::ParsedDicomFile& dicom_; + size_t fileSize_; + bool hasPixelData_; + + public: + ParseDicomSuccessMessage(const OracleCommandBase& command, + Orthanc::ParsedDicomFile& dicom, + size_t fileSize, + bool hasPixelData) : + OriginMessage(command), + dicom_(dicom), + fileSize_(fileSize), + hasPixelData_(hasPixelData) + { + } + + Orthanc::ParsedDicomFile& GetDicom() const + { + return dicom_; + } + + size_t GetFileSize() const + { + return fileSize_; + } + + bool HasPixelData() const + { + return hasPixelData_; + } + + static Orthanc::ParsedDicomFile* ParseWadoAnswer(size_t& fileSize /* OUT */, + const std::string& answer, + const std::map& headers); + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/ReadFileCommand.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ReadFileCommand.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Messages/IMessage.h" +#include "OracleCommandBase.h" + +namespace OrthancStone +{ + class ReadFileCommand : public OracleCommandBase + { + public: + class SuccessMessage : public OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + const std::string& content_; + + public: + SuccessMessage(const ReadFileCommand& command, + const std::string& content) : + OriginMessage(command), + content_(content) + { + } + + const std::string& GetContent() const + { + return content_; + } + }; + + + private: + std::string path_; + + public: + ReadFileCommand(const std::string& path) : + path_(path) + { + } + + virtual Type GetType() const + { + return Type_ReadFile; + } + + virtual IOracleCommand* Clone() const + { + return new ReadFileCommand(path_); + } + + const std::string& GetPath() const + { + return path_; + } + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/SleepOracleCommand.h --- a/Framework/Oracle/SleepOracleCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/SleepOracleCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,11 +22,11 @@ #pragma once #include "../Messages/IMessage.h" -#include "OracleCommandWithPayload.h" +#include "OracleCommandBase.h" namespace OrthancStone { - class SleepOracleCommand : public OracleCommandWithPayload + class SleepOracleCommand : public OracleCommandBase { private: unsigned int milliseconds_; @@ -44,6 +44,11 @@ return Type_Sleep; } + virtual IOracleCommand* Clone() const + { + return new SleepOracleCommand(milliseconds_); + } + unsigned int GetDelay() const { return milliseconds_; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/ThreadedOracle.cpp --- a/Framework/Oracle/ThreadedOracle.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/ThreadedOracle.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,29 +21,21 @@ #include "ThreadedOracle.h" -#include "GetOrthancImageCommand.h" -#include "GetOrthancWebViewerJpegCommand.h" -#include "HttpCommand.h" -#include "OrthancRestApiCommand.h" #include "SleepOracleCommand.h" -#include "OracleCommandExceptionMessage.h" -#include -#include +#include #include -#include - namespace OrthancStone { class ThreadedOracle::Item : public Orthanc::IDynamicObject { private: - const IObserver& receiver_; + boost::weak_ptr receiver_; std::unique_ptr command_; public: - Item(const IObserver& receiver, + Item(boost::weak_ptr receiver, IOracleCommand* command) : receiver_(receiver), command_(command) @@ -54,7 +46,7 @@ } } - const IObserver& GetReceiver() const + boost::weak_ptr GetReceiver() { return receiver_; } @@ -73,12 +65,12 @@ class Item { private: - const IObserver& receiver_; + boost::weak_ptr receiver_; std::unique_ptr command_; boost::posix_time::ptime expiration_; public: - Item(const IObserver& receiver, + Item(boost::weak_ptr receiver, SleepOracleCommand* command) : receiver_(receiver), command_(command) @@ -123,7 +115,7 @@ } } - void Add(const IObserver& receiver, + void Add(boost::weak_ptr receiver, SleepOracleCommand* command) // Takes ownership { boost::mutex::scoped_lock lock(mutex_); @@ -160,154 +152,6 @@ }; - 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 IObserver& receiver, - const HttpCommand& command) - { - Orthanc::HttpClient client; - client.SetUrl(command.GetUrl()); - 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); - - HttpCommand::SuccessMessage message(command, answerHeaders, answer); - emitter.EmitMessage(receiver, message); - } - - - 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::unique_ptr object(queue_.Dequeue(100)); @@ -316,60 +160,37 @@ { Item& item = dynamic_cast(*object); - try + if (item.GetCommand().GetType() == IOracleCommand::Type_Sleep) { - switch (item.GetCommand().GetType()) + SleepOracleCommand& command = dynamic_cast(item.GetCommand()); + + std::unique_ptr copy(new SleepOracleCommand(command.GetDelay())); + + if (command.HasPayload()) { - case IOracleCommand::Type_Sleep: - { - SleepOracleCommand& command = dynamic_cast(item.GetCommand()); - - std::unique_ptr copy(new SleepOracleCommand(command.GetDelay())); - - if (command.HasPayload()) - { - copy->SetPayload(command.ReleasePayload()); - } - - sleepingCommands_->Add(item.GetReceiver(), copy.release()); - - break; - } - - case IOracleCommand::Type_Http: - Execute(emitter_, item.GetReceiver(), - dynamic_cast(item.GetCommand())); - break; + copy->AcquirePayload(command.ReleasePayload()); + } + + sleepingCommands_->Add(item.GetReceiver(), copy.release()); + } + else + { + GenericOracleRunner runner; - case IOracleCommand::Type_OrthancRestApi: - Execute(emitter_, orthanc_, item.GetReceiver(), - dynamic_cast(item.GetCommand())); - break; - - case IOracleCommand::Type_GetOrthancImage: - Execute(emitter_, orthanc_, item.GetReceiver(), - dynamic_cast(item.GetCommand())); - break; - - case IOracleCommand::Type_GetOrthancWebViewerJpeg: - Execute(emitter_, orthanc_, item.GetReceiver(), - dynamic_cast(item.GetCommand())); - break; + { + boost::mutex::scoped_lock lock(mutex_); + runner.SetOrthanc(orthanc_); + runner.SetRootDirectory(rootDirectory_); - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); +#if ORTHANC_ENABLE_DCMTK == 1 + if (dicomCache_) + { + runner.SetDicomCache(dicomCache_); + } +#endif } - } - 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)); + + runner.Run(item.GetReceiver(), emitter_, item.GetCommand()); } } } @@ -453,6 +274,7 @@ ThreadedOracle::ThreadedOracle(IMessageEmitter& emitter) : emitter_(emitter), + rootDirectory_("."), state_(State_Setup), workers_(4), sleepingCommands_(new SleepingCommands), @@ -480,23 +302,21 @@ catch (...) { LOG(ERROR) << "Native exception while stopping the threaded oracle"; - } + } } void ThreadedOracle::SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc) { boost::mutex::scoped_lock lock(mutex_); + orthanc_ = orthanc; + } - if (state_ != State_Setup) - { - LOG(ERROR) << "ThreadedOracle::SetOrthancParameters(): (state_ != State_Setup)"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - orthanc_ = orthanc; - } + + void ThreadedOracle::SetRootDirectory(const std::string& rootDirectory) + { + boost::mutex::scoped_lock lock(mutex_); + rootDirectory_ = rootDirectory; } @@ -540,6 +360,31 @@ } + void ThreadedOracle::SetDicomCacheSize(size_t size) + { +#if ORTHANC_ENABLE_DCMTK == 1 + boost::mutex::scoped_lock lock(mutex_); + + if (state_ != State_Setup) + { + LOG(ERROR) << "ThreadedOracle::SetDicomCacheSize(): (state_ != State_Setup)"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + if (size == 0) + { + dicomCache_.reset(); + } + else + { + dicomCache_.reset(new ParsedDicomCache(size)); + } + } +#endif + } + + void ThreadedOracle::Start() { boost::mutex::scoped_lock lock(mutex_); @@ -551,6 +396,7 @@ } else { + LOG(WARNING) << "Starting oracle with " << workers_.size() << " worker threads"; state_ = State_Running; for (unsigned int i = 0; i < workers_.size(); i++) @@ -563,9 +409,25 @@ } - void ThreadedOracle::Schedule(const IObserver& receiver, + bool ThreadedOracle::Schedule(boost::shared_ptr receiver, IOracleCommand* command) { - queue_.Enqueue(new Item(receiver, command)); + std::unique_ptr item(new Item(receiver, command)); + + { + boost::mutex::scoped_lock lock(mutex_); + + if (state_ == State_Running) + { + //LOG(INFO) << "New oracle command queued"; + queue_.Enqueue(item.release()); + return true; + } + else + { + LOG(TRACE) << "Command not enqueued, as the oracle has stopped"; + return false; + } + } } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/ThreadedOracle.h --- a/Framework/Oracle/ThreadedOracle.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/ThreadedOracle.h Wed Apr 22 14:05:47 2020 +0200 @@ -25,14 +25,22 @@ # error The macro ORTHANC_ENABLE_THREADS must be defined #endif +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + #if ORTHANC_ENABLE_THREADS != 1 # error This file can only compiled for native targets #endif -#include "../Messages/IMessageEmitter.h" +#if ORTHANC_ENABLE_DCMTK == 1 +# include "../Toolbox/ParsedDicomCache.h" +#endif + #include "IOracle.h" +#include "GenericOracleRunner.h" +#include "../Messages/IMessageEmitter.h" -#include #include @@ -53,6 +61,7 @@ IMessageEmitter& emitter_; Orthanc::WebServiceParameters orthanc_; + std::string rootDirectory_; Orthanc::SharedMessageQueue queue_; State state_; boost::mutex mutex_; @@ -61,6 +70,10 @@ boost::thread sleepingWorker_; unsigned int sleepingTimeResolution_; +#if ORTHANC_ENABLE_DCMTK == 1 + boost::shared_ptr dicomCache_; +#endif + void Step(); static void Worker(ThreadedOracle* that); @@ -74,13 +87,16 @@ virtual ~ThreadedOracle(); - // The reference is not stored. void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc); + void SetRootDirectory(const std::string& rootDirectory); + void SetThreadsCount(unsigned int count); void SetSleepingTimeResolution(unsigned int milliseconds); + void SetDicomCacheSize(size_t size); + void Start(); void Stop() @@ -88,7 +104,7 @@ StopInternal(); } - virtual void Schedule(const IObserver& receiver, - IOracleCommand* command); + virtual bool Schedule(boost::shared_ptr receiver, + IOracleCommand* command) ORTHANC_OVERRIDE; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/WebAssemblyOracle.cpp --- a/Framework/Oracle/WebAssemblyOracle.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/WebAssemblyOracle.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,8 +21,13 @@ #include "WebAssemblyOracle.h" +#include "OracleCommandExceptionMessage.h" #include "SleepOracleCommand.h" -#include "OracleCommandExceptionMessage.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "ParseDicomSuccessMessage.h" +static unsigned int BUCKET_SOP = 1; +#endif #include #include @@ -31,23 +36,18 @@ #include #include -#if 0 -extern bool logbgo233; -extern bool logbgo115; -#endif - namespace OrthancStone { class WebAssemblyOracle::TimeoutContext { private: WebAssemblyOracle& oracle_; - const IObserver& receiver_; + boost::weak_ptr receiver_; std::unique_ptr command_; public: TimeoutContext(WebAssemblyOracle& oracle, - const IObserver& receiver, + boost::weak_ptr receiver, IOracleCommand* command) : oracle_(oracle), receiver_(receiver) @@ -63,7 +63,9 @@ } void EmitMessage() - { + { + assert(command_.get() != NULL); + SleepOracleCommand::TimeoutMessage message(*command_); oracle_.EmitMessage(receiver_, message); } @@ -76,26 +78,6 @@ }; - class WebAssemblyOracle::Emitter : public IMessageEmitter - { - private: - WebAssemblyOracle& oracle_; - - public: - Emitter(WebAssemblyOracle& oracle) : - oracle_(oracle) - { - } - - virtual void EmitMessage(const IObserver& receiver, - const IMessage& message) - { - LOG(TRACE) << "WebAssemblyOracle::Emitter::EmitMessage receiver = " - << std::hex << &receiver << std::dec; - oracle_.EmitMessage(receiver, message); - } - }; - /** This object is created on the heap for every http request. It is deleted in the success (or error) callbacks. @@ -108,26 +90,23 @@ class WebAssemblyOracle::FetchContext : public boost::noncopyable { private: - Emitter emitter_; - const IObserver& receiver_; - std::unique_ptr command_; - std::string expectedContentType_; - int64_t receiverFingerprint_; + WebAssemblyOracle& oracle_; + boost::weak_ptr receiver_; + std::unique_ptr command_; + std::string expectedContentType_; public: FetchContext(WebAssemblyOracle& oracle, - const IObserver& receiver, + boost::weak_ptr receiver, IOracleCommand* command, const std::string& expectedContentType) : - emitter_(oracle), + oracle_(oracle), receiver_(receiver), command_(command), - expectedContentType_(expectedContentType), - receiverFingerprint_(receiver.GetFingerprint()) + expectedContentType_(expectedContentType) { LOG(TRACE) << "WebAssemblyOracle::FetchContext::FetchContext() | " - << "receiver address = " << std::hex << &receiver << std::dec - << " with fingerprint = " << receiverFingerprint_; + << "receiver address = " << std::hex << &receiver; if (command == NULL) { @@ -140,21 +119,21 @@ return expectedContentType_; } + IMessageEmitter& GetEmitter() const + { + return oracle_; + } + + boost::weak_ptr GetReceiver() const + { + return receiver_; + } + void EmitMessage(const IMessage& message) { LOG(TRACE) << "WebAssemblyOracle::FetchContext::EmitMessage receiver_ = " << std::hex << &receiver_ << std::dec; - emitter_.EmitMessage(receiver_, message); - } - - IMessageEmitter& GetEmitter() - { - return emitter_; - } - - const IObserver& GetReceiver() const - { - return receiver_; + oracle_.EmitMessage(receiver_, message); } IOracleCommand& GetCommand() const @@ -168,41 +147,17 @@ return dynamic_cast(*command_); } -#if 0 - static std::string ToString(Orthanc::HttpMethod method) - { - switch (method) { - case Orthanc::HttpMethod_Get: - return "GET"; - break; - case Orthanc::HttpMethod_Post: - return "POST"; - break; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - break; - } - } - static void DumpCommand(emscripten_fetch_t* fetch, std::string answer) +#if ORTHANC_ENABLE_DCMTK == 1 + void StoreInCache(const std::string& sopInstanceUid, + std::unique_ptr& dicom, + size_t fileSize) { - FetchContext* context = reinterpret_cast(fetch->userData); - - const auto& command = context->GetTypedCommand(); - auto commandStr = ToString(command.GetMethod()); - LOG(TRACE) << "SuccessCallback for REST command. Method is : " << commandStr; - switch (command.GetMethod()) { - case Orthanc::HttpMethod_Get: - LOG(TRACE) << " * SuccessCallback GET URI = " << command.GetUri() << " timeout = " << command.GetTimeout(); - LOG(TRACE) << " * SuccessCallback GET RESPONSE = " << answer; - break; - case Orthanc::HttpMethod_Post: - LOG(TRACE) << " * SuccessCallback POST URI = " << command.GetUri() << " body = " << command.GetBody() << " timeout = " << command.GetTimeout(); - LOG(TRACE) << " * SuccessCallback POST RESPONSE = " << answer; - break; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - break; - } + if (oracle_.dicomCache_.get()) + { + // Store it into the cache for future use + oracle_.dicomCache_->Acquire(BUCKET_SOP, sopInstanceUid, + dicom.release(), fileSize, true); + } } #endif @@ -213,64 +168,43 @@ * free data associated with the fetch. **/ - std::unique_ptr context(reinterpret_cast(fetch->userData)); - - // an UUID is 36 chars : 32 hex chars + 4 hyphens: char #0 --> char #35 - // char #36 is \0. - bool callHandler = true; - - // TODO: remove this line because we are NOT allowed to call methods on GetReceiver that is maybe a dangling ref - if (context->GetReceiver().DoesFingerprintLookGood()) - { - callHandler = true; - int64_t currentFingerprint(context->GetReceiver().GetFingerprint()); - - LOG(TRACE) << "SuccessCallback for object at address (" << std::hex - << &(context->GetReceiver()) << std::dec - << " with current fingerprint = " << currentFingerprint - << ". Fingerprint looks OK"; - - if (currentFingerprint != context->receiverFingerprint_) - { - LOG(TRACE) << " ** SuccessCallback: BUT currentFingerprint != " - << "receiverFingerprint_(" << context->receiverFingerprint_ << ")"; - callHandler = false; - } - else - { - LOG(TRACE) << " ** SuccessCallback: FetchContext-level " - << "fingerprints are the same: " - << context->receiverFingerprint_ - << " ---> oracle will dispatch the response to observer: " - << std::hex << &(context->GetReceiver()) << std::dec; - } - } - else { - LOG(TRACE) << "SuccessCallback for object at address (" << std::hex << &(context->GetReceiver()) << std::dec << " with current fingerprint is XXXXX -- NOT A VALID FINGERPRINT! OBJECT IS READ ! CALLBACK WILL NOT BE CALLED!"; - callHandler = false; - } - if (fetch->userData == NULL) { LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback fetch->userData is NULL!!!!!!!"; + return; } + std::unique_ptr context(reinterpret_cast(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. + * Retrieving the headers of the HTTP answer. + **/ + HttpHeaders headers; + +#if (__EMSCRIPTEN_major__ < 1 || \ + (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ < 38) || \ + (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ < 37)) +# warning Consider upgrading Emscripten to a version above 1.38.37, incomplete support of Fetch API + + /** + * HACK - If emscripten < 1.38.37, 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 is fixed thanks to the + * "emscripten_fetch_get_response_headers()" function that was + * added to "fetch.h" at emscripten-1.38.37 on 2019-06-26. + * + * https://github.com/emscripten-core/emscripten/blob/1.38.37/system/include/emscripten/fetch.h * https://github.com/emscripten-core/emscripten/pull/8486 **/ - - HttpHeaders headers; if (fetch->userData != NULL) { if (!context->GetExpectedContentType().empty()) @@ -278,13 +212,29 @@ headers["Content-Type"] = context->GetExpectedContentType(); } } - -#if 0 - if (context->GetCommand().GetType() == IOracleCommand::Type_OrthancRestApi) { - //if (logbgo115) - DumpCommand(fetch, answer); +#else + { + size_t size = emscripten_fetch_get_response_headers_length(fetch); + + std::string plainHeaders(size + 1, '\0'); + emscripten_fetch_get_response_headers(fetch, &plainHeaders[0], size + 1); + + std::vector tokens; + Orthanc::Toolbox::TokenizeString(tokens, plainHeaders, '\n'); + + for (size_t i = 0; i < tokens.size(); i++) + { + size_t p = tokens[i].find(':'); + if (p != std::string::npos) + { + std::string key = Orthanc::Toolbox::StripSpaces(tokens[i].substr(0, p)); + std::string value = Orthanc::Toolbox::StripSpaces(tokens[i].substr(p + 1)); + headers[key] = value; + } + } } #endif + LOG(TRACE) << "About to call emscripten_fetch_close"; emscripten_fetch_close(fetch); LOG(TRACE) << "Successfully called emscripten_fetch_close"; @@ -303,50 +253,75 @@ } else { - if (callHandler) + switch (context->GetCommand().GetType()) { - switch (context->GetCommand().GetType()) + case IOracleCommand::Type_Http: + { + HttpCommand::SuccessMessage message(context->GetTypedCommand(), headers, answer); + context->EmitMessage(message); + break; + } + + case IOracleCommand::Type_OrthancRestApi: + { + LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call context->EmitMessage(message);"; + OrthancRestApiCommand::SuccessMessage message + (context->GetTypedCommand(), headers, answer); + context->EmitMessage(message); + break; + } + + case IOracleCommand::Type_GetOrthancImage: { - case IOracleCommand::Type_Http: + context->GetTypedCommand().ProcessHttpAnswer + (context->GetReceiver(), context->GetEmitter(), answer, headers); + break; + } + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + { + context->GetTypedCommand().ProcessHttpAnswer + (context->GetReceiver(), context->GetEmitter(), answer); + break; + } + + case IOracleCommand::Type_ParseDicomFromWado: + { +#if ORTHANC_ENABLE_DCMTK == 1 + const ParseDicomFromWadoCommand& command = + context->GetTypedCommand(); + + size_t fileSize; + std::unique_ptr dicom + (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers)); + { - HttpCommand::SuccessMessage message(context->GetTypedCommand(), headers, answer); + ParseDicomSuccessMessage message(command, *dicom, fileSize, true); context->EmitMessage(message); - break; } - case IOracleCommand::Type_OrthancRestApi: - { - LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call context->EmitMessage(message);"; - OrthancRestApiCommand::SuccessMessage message - (context->GetTypedCommand(), headers, answer); - context->EmitMessage(message); - break; - } + context->StoreInCache(command.GetSopInstanceUid(), dicom, fileSize); +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); +#endif + break; + } - case IOracleCommand::Type_GetOrthancImage: - { - context->GetTypedCommand().ProcessHttpAnswer - (context->GetEmitter(), context->GetReceiver(), answer, headers); - break; - } - - case IOracleCommand::Type_GetOrthancWebViewerJpeg: - { - context->GetTypedCommand().ProcessHttpAnswer - (context->GetEmitter(), context->GetReceiver(), answer); - break; - } - - default: - LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " - << context->GetCommand().GetType(); - } + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): " + << context->GetCommand().GetType(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } } } catch (Orthanc::OrthancException& e) { - LOG(ERROR) << "Error while processing a fetch answer in the oracle: " << e.What(); + LOG(INFO) << "Error while processing a fetch answer in the oracle: " << e.What(); + + { + OracleCommandExceptionMessage message(context->GetCommand(), e); + context->EmitMessage(message); + } } } @@ -388,7 +363,7 @@ { private: WebAssemblyOracle& oracle_; - const IObserver& receiver_; + boost::weak_ptr receiver_; std::unique_ptr command_; Orthanc::HttpMethod method_; std::string url_; @@ -396,16 +371,20 @@ HttpHeaders headers_; unsigned int timeout_; std::string expectedContentType_; + bool hasCredentials_; + std::string username_; + std::string password_; public: FetchCommand(WebAssemblyOracle& oracle, - const IObserver& receiver, + boost::weak_ptr receiver, IOracleCommand* command) : oracle_(oracle), receiver_(receiver), command_(command), method_(Orthanc::HttpMethod_Get), - timeout_(0) + timeout_(0), + hasCredentials_(false) { if (command == NULL) { @@ -418,11 +397,6 @@ method_ = method; } - void SetOrthancUri(const std::string& uri) - { - url_ = oracle_.orthancRoot_ + uri; - } - void SetUrl(const std::string& url) { url_ = url; @@ -433,9 +407,12 @@ body_.swap(body); } - void SetHttpHeaders(const HttpHeaders& headers) + void AddHttpHeaders(const HttpHeaders& headers) { - headers_ = headers; + for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) + { + headers_[it->first] = it->second; + } } void SetTimeout(unsigned int timeout) @@ -443,15 +420,16 @@ timeout_ = timeout; } + void SetCredentials(const std::string& username, + const std::string& password) + { + hasCredentials_ = true; + username_ = username; + password_ = password; + } + void Execute() { -#if 0 - if (logbgo233) { - if (logbgo115) - LOG(TRACE) << " WebAssemblyOracle::Execute () command addr " << - std::hex << command_.get() << std::dec; - } -#endif if (command_.get() == NULL) { // Cannot call Execute() twice @@ -493,6 +471,13 @@ attr.onerror = FetchContext::FailureCallback; attr.timeoutMSecs = timeout_ * 1000; + if (hasCredentials_) + { + attr.withCredentials = EM_TRUE; + attr.userName = username_.c_str(); + attr.password = password_.c_str(); + } + std::vector headers; headers.reserve(2 * headers_.size() + 1); @@ -534,9 +519,6 @@ attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); // Must be the last call to prevent memory leak on error -#if 0 - LOG(TRACE) << "Performing " << method << " request on URI: \"" << url_ << "\""; -#endif emscripten_fetch(&attr, url_.c_str()); } catch(...) @@ -548,34 +530,35 @@ } }; -#if 0 - static void DumpCommand(OrthancRestApiCommand* pCommand) + + void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command, + const std::string& uri) const { - OrthancRestApiCommand& command = *pCommand; - LOG(TRACE) << "WebAssemblyOracle::Execute for REST command."; - switch (command.GetMethod()) { - case Orthanc::HttpMethod_Get: - LOG(TRACE) << " * WebAssemblyOracle::Execute GET URI = " << command.GetUri() << " timeout = " << command.GetTimeout(); - break; - case Orthanc::HttpMethod_Post: - LOG(TRACE) << " * WebAssemblyOracle::Execute POST URI = " << command.GetUri() << " body = " << command.GetBody() << " timeout = " << command.GetTimeout(); - break; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - break; + if (isLocalOrthanc_) + { + command.SetUrl(localOrthancRoot_ + uri); + } + else + { + command.SetUrl(remoteOrthanc_.GetUrl() + uri); + command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders()); + + if (!remoteOrthanc_.GetUsername().empty()) + { + command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword()); + } } } -#endif + - - void WebAssemblyOracle::Execute(const IObserver& receiver, + void WebAssemblyOracle::Execute(boost::weak_ptr receiver, HttpCommand* command) { FetchCommand fetch(*this, receiver, command); fetch.SetMethod(command->GetMethod()); fetch.SetUrl(command->GetUrl()); - fetch.SetHttpHeaders(command->GetHttpHeaders()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); fetch.SetTimeout(command->GetTimeout()); if (command->GetMethod() == Orthanc::HttpMethod_Post || @@ -590,19 +573,9 @@ } - void WebAssemblyOracle::Execute(const IObserver& receiver, + void WebAssemblyOracle::Execute(boost::weak_ptr receiver, OrthancRestApiCommand* command) { -#if 0 - DumpCommand(command); - - if (logbgo233) { - if (logbgo115) - LOG(TRACE) << " WebAssemblyOracle::Execute (OrthancRestApiCommand) command addr " << - std::hex << command << std::dec; - } -#endif - try { //LOG(TRACE) << "*********** WebAssemblyOracle::Execute."; @@ -610,8 +583,8 @@ FetchCommand fetch(*this, receiver, command); fetch.SetMethod(command->GetMethod()); - fetch.SetOrthancUri(command->GetUri()); - fetch.SetHttpHeaders(command->GetHttpHeaders()); + SetOrthancUrl(fetch, command->GetUri()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); fetch.SetTimeout(command->GetTimeout()); if (command->GetMethod() == Orthanc::HttpMethod_Post || @@ -653,54 +626,112 @@ } - void WebAssemblyOracle::Execute(const IObserver& receiver, + void WebAssemblyOracle::Execute(boost::weak_ptr receiver, GetOrthancImageCommand* command) { -#if 0 - if (logbgo233) { - if (logbgo115) - LOG(TRACE) << " WebAssemblyOracle::Execute (GetOrthancImageCommand) command addr " << - std::hex << command << std::dec; - } -#endif - FetchCommand fetch(*this, receiver, command); - fetch.SetOrthancUri(command->GetUri()); - fetch.SetHttpHeaders(command->GetHttpHeaders()); + SetOrthancUrl(fetch, command->GetUri()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); fetch.SetTimeout(command->GetTimeout()); fetch.Execute(); } - void WebAssemblyOracle::Execute(const IObserver& receiver, + void WebAssemblyOracle::Execute(boost::weak_ptr receiver, GetOrthancWebViewerJpegCommand* command) { -#if 0 - if (logbgo233) { - if (logbgo115) - LOG(TRACE) << " WebAssemblyOracle::Execute (GetOrthancWebViewerJpegCommand) command addr " << std::hex << command << std::dec; - } -#endif - FetchCommand fetch(*this, receiver, command); - fetch.SetOrthancUri(command->GetUri()); - fetch.SetHttpHeaders(command->GetHttpHeaders()); + SetOrthancUrl(fetch, command->GetUri()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); fetch.SetTimeout(command->GetTimeout()); fetch.Execute(); } + void WebAssemblyOracle::Execute(boost::weak_ptr receiver, + ParseDicomFromWadoCommand* command) + { + std::unique_ptr protection(command); + +#if ORTHANC_ENABLE_DCMTK == 1 + if (dicomCache_.get()) + { + ParsedDicomCache::Reader reader(*dicomCache_, BUCKET_SOP, protection->GetSopInstanceUid()); + if (reader.IsValid() && + reader.HasPixelData()) + { + // Reuse the DICOM file from the cache + ParseDicomSuccessMessage message(*protection, reader.GetDicom(), + reader.GetFileSize(), reader.HasPixelData()); + EmitMessage(receiver, message); + return; + } + } +#endif - void WebAssemblyOracle::Schedule(const IObserver& receiver, + switch (command->GetRestCommand().GetType()) + { + case IOracleCommand::Type_Http: + { + const HttpCommand& rest = + dynamic_cast(protection->GetRestCommand()); + + FetchCommand fetch(*this, receiver, protection.release()); + + fetch.SetMethod(rest.GetMethod()); + fetch.SetUrl(rest.GetUrl()); + fetch.AddHttpHeaders(rest.GetHttpHeaders()); + fetch.SetTimeout(rest.GetTimeout()); + + if (rest.GetMethod() == Orthanc::HttpMethod_Post || + rest.GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body = rest.GetBody(); + fetch.SetBody(body); + } + + fetch.Execute(); + break; + } + + case IOracleCommand::Type_OrthancRestApi: + { + const OrthancRestApiCommand& rest = + dynamic_cast(protection->GetRestCommand()); + + FetchCommand fetch(*this, receiver, protection.release()); + + fetch.SetMethod(rest.GetMethod()); + SetOrthancUrl(fetch, rest.GetUri()); + fetch.AddHttpHeaders(rest.GetHttpHeaders()); + fetch.SetTimeout(rest.GetTimeout()); + + if (rest.GetMethod() == Orthanc::HttpMethod_Post || + rest.GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body = rest.GetBody(); + fetch.SetBody(body); + } + + fetch.Execute(); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + bool WebAssemblyOracle::Schedule(boost::shared_ptr receiver, IOracleCommand* command) { LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = " - << std::hex << &receiver << std::dec - << " | Current fingerprint is " << receiver.GetFingerprint(); + << std::hex << &receiver; std::unique_ptr protection(command); @@ -716,37 +747,14 @@ break; case IOracleCommand::Type_OrthancRestApi: - //// DIAGNOSTIC. PLEASE REMOVE IF IT HAS BEEN COMMITTED BY MISTAKE - //{ - // const IObserver* pReceiver = &receiver; - // LOG(TRACE) << "WebAssemblyOracle::Schedule | pReceiver is " << pReceiver; - // LOG(TRACE) << "WebAssemblyOracle::Schedule | command = " << command; - // OrthancRestApiCommand* rac = dynamic_cast(protection.get()); - // LOG(TRACE) << "WebAssemblyOracle::Schedule | typed command = " << rac; - // LOG(TRACE) << "WebAssemblyOracle::Schedule" << rac->GetUri(); - //} - //// END OF BLOCK TO REMOVE Execute(receiver, dynamic_cast(protection.release())); break; case IOracleCommand::Type_GetOrthancImage: - //// DIAGNOSTIC. PLEASE REMOVE IF IT HAS BEEN COMMITTED BY MISTAKE - //{ - // GetOrthancImageCommand* rac = dynamic_cast(protection.get()); - // LOG(TRACE) << "WebAssemblyOracle::Schedule" << rac->GetUri(); - //} - //// END OF BLOCK TO REMOVE Execute(receiver, dynamic_cast(protection.release())); break; case IOracleCommand::Type_GetOrthancWebViewerJpeg: - //// DIAGNOSTIC. PLEASE REMOVE IF IT HAS BEEN COMMITTED BY MISTAKE - //{ - // GetOrthancWebViewerJpegCommand* rac = dynamic_cast(protection.get()); - // LOG(TRACE) << "WebAssemblyOracle::Schedule" << rac->GetUri(); - //} - //// END OF BLOCK TO REMOVE - Execute(receiver, dynamic_cast(protection.release())); break; case IOracleCommand::Type_Sleep: @@ -757,8 +765,38 @@ break; } + case IOracleCommand::Type_ParseDicomFromWado: +#if ORTHANC_ENABLE_DCMTK == 1 + Execute(receiver, dynamic_cast(protection.release())); +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "DCMTK must be enabled to parse DICOM files"); +#endif + break; + default: - LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " << command->GetType(); + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): " + << command->GetType(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } + + return true; + } + + + void WebAssemblyOracle::SetDicomCacheSize(size_t size) + { +#if ORTHANC_ENABLE_DCMTK == 1 + if (size == 0) + { + dicomCache_.reset(); + } + else + { + dicomCache_.reset(new ParsedDicomCache(size)); + } +#else + LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled"; +#endif } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Oracle/WebAssemblyOracle.h --- a/Framework/Oracle/WebAssemblyOracle.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Oracle/WebAssemblyOracle.h Wed Apr 22 14:05:47 2020 +0200 @@ -35,48 +35,87 @@ #include "HttpCommand.h" #include "IOracle.h" #include "OrthancRestApiCommand.h" +#include "ParseDicomFromWadoCommand.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "../Toolbox/ParsedDicomCache.h" +#endif + +#include namespace OrthancStone { class WebAssemblyOracle : public IOracle, - public IObservable + public IMessageEmitter { private: typedef std::map HttpHeaders; class TimeoutContext; - class Emitter; class FetchContext; - class FetchCommand; + class FetchCommand; + + void SetOrthancUrl(FetchCommand& command, + const std::string& uri) const; - void Execute(const IObserver& receiver, + void Execute(boost::weak_ptr receiver, HttpCommand* command); - void Execute(const IObserver& receiver, + void Execute(boost::weak_ptr receiver, OrthancRestApiCommand* command); - void Execute(const IObserver& receiver, + void Execute(boost::weak_ptr receiver, GetOrthancImageCommand* command); - void Execute(const IObserver& receiver, + void Execute(boost::weak_ptr receiver, GetOrthancWebViewerJpegCommand* command); + + void Execute(boost::weak_ptr receiver, + ParseDicomFromWadoCommand* command); - std::string orthancRoot_; + IObservable oracleObservable_; + bool isLocalOrthanc_; + std::string localOrthancRoot_; + Orthanc::WebServiceParameters remoteOrthanc_; + +#if ORTHANC_ENABLE_DCMTK == 1 + std::unique_ptr dicomCache_; +#endif public: - WebAssemblyOracle(MessageBroker& broker) : - IObservable(broker) + WebAssemblyOracle() : + isLocalOrthanc_(false) { } - - void SetOrthancRoot(const std::string& root) + + virtual void EmitMessage(boost::weak_ptr observer, + const IMessage& message) ORTHANC_OVERRIDE { - orthancRoot_ = root; + oracleObservable_.EmitMessage(observer, message); } - virtual void Schedule(const IObserver& receiver, - IOracleCommand* command); + virtual bool Schedule(boost::shared_ptr receiver, + IOracleCommand* command) ORTHANC_OVERRIDE; + + IObservable& GetOracleObservable() + { + return oracleObservable_; + } + + void SetLocalOrthanc(const std::string& root) + { + isLocalOrthanc_ = true; + localOrthancRoot_ = root; + } + + void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc) + { + isLocalOrthanc_ = false; + remoteOrthanc_ = orthanc; + } + + void SetDicomCacheSize(size_t size); }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyAlphaLayer.cpp diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyAlphaLayer.h --- a/Framework/Radiography/RadiographyAlphaLayer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyAlphaLayer.h Wed Apr 22 14:05:47 2020 +0200 @@ -39,8 +39,8 @@ float foreground_; // in the range [0.0, 65535.0] public: - RadiographyAlphaLayer(MessageBroker& broker, const RadiographyScene& scene) : - RadiographyLayer(broker, scene), + RadiographyAlphaLayer(const RadiographyScene& scene) : + RadiographyLayer(scene), foreground_(0) { } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyDicomLayer.cpp --- a/Framework/Radiography/RadiographyDicomLayer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -48,7 +48,8 @@ } - RadiographyDicomLayer::RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene) : RadiographyLayer(broker, scene) + RadiographyDicomLayer::RadiographyDicomLayer(const RadiographyScene& scene) : + RadiographyLayer(scene) { } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyDicomLayer.h --- a/Framework/Radiography/RadiographyDicomLayer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.h Wed Apr 22 14:05:47 2020 +0200 @@ -42,7 +42,7 @@ void ApplyConverter(); public: - RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene); + RadiographyDicomLayer(const RadiographyScene& scene); void SetInstance(const std::string& instanceId, unsigned int frame) { diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyLayer.cpp --- a/Framework/Radiography/RadiographyLayer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyLayer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -119,8 +119,7 @@ } - RadiographyLayer::RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene) : - IObservable(broker), + RadiographyLayer::RadiographyLayer(const RadiographyScene& scene) : index_(0), hasSize_(false), width_(0), diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyLayer.h --- a/Framework/Radiography/RadiographyLayer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyLayer.h Wed Apr 22 14:05:47 2020 +0200 @@ -240,7 +240,7 @@ double zoom); public: - RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene); + RadiographyLayer(const RadiographyScene& scene); virtual ~RadiographyLayer() { diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyMaskLayer.h --- a/Framework/Radiography/RadiographyMaskLayer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyMaskLayer.h Wed Apr 22 14:05:47 2020 +0200 @@ -42,9 +42,9 @@ mutable std::unique_ptr mask_; public: - RadiographyMaskLayer(MessageBroker& broker, const RadiographyScene& scene, const RadiographyDicomLayer& dicomLayer, + RadiographyMaskLayer(const RadiographyScene& scene, const RadiographyDicomLayer& dicomLayer, float foreground) : - RadiographyLayer(broker, scene), + RadiographyLayer(scene), dicomLayer_(dicomLayer), invalidated_(true), foreground_(foreground) diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyScene.cpp --- a/Framework/Radiography/RadiographyScene.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyScene.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -150,7 +150,7 @@ BroadcastMessage(GeometryChangedMessage(*this, *layer)); BroadcastMessage(ContentChangedMessage(*this, *layer)); - layer->RegisterObserverCallback(new Callable(*this, &RadiographyScene::OnLayerEdited)); + Register(*layer, &RadiographyScene::OnLayerEdited); return *layer; } @@ -170,9 +170,8 @@ BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin())); } - RadiographyScene::RadiographyScene(MessageBroker& broker) : - IObserver(broker), - IObservable(broker), + + RadiographyScene::RadiographyScene() : nextLayerIndex_(0), hasWindowing_(false), windowingCenter_(0), // Dummy initialization @@ -328,7 +327,7 @@ RadiographyLayer::Geometry* centerGeometry, bool isCenterGeometry) { - std::unique_ptr alpha(new RadiographyTextLayer(IObservable::GetBroker(), *this)); + std::unique_ptr alpha(new RadiographyTextLayer(*this)); alpha->SetText(utf8, font, fontSize, foreground); if (centerGeometry != NULL) { @@ -385,7 +384,7 @@ float foreground, RadiographyLayer::Geometry* geometry) { - std::unique_ptr mask(new RadiographyMaskLayer(IObservable::GetBroker(), *this, dicomLayer, foreground)); + std::unique_ptr mask(new RadiographyMaskLayer(*this, dicomLayer, foreground)); mask->SetCorners(corners); if (geometry != NULL) { @@ -398,7 +397,7 @@ RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry) { - std::unique_ptr alpha(new RadiographyAlphaLayer(IObservable::GetBroker(), *this)); + std::unique_ptr alpha(new RadiographyAlphaLayer(*this)); alpha->SetAlpha(bitmap); if (geometry != NULL) { @@ -415,7 +414,7 @@ RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode, RadiographyLayer::Geometry* geometry) { - RadiographyDicomLayer& layer = dynamic_cast(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this))); + RadiographyDicomLayer& layer = dynamic_cast(RegisterLayer(new RadiographyDicomLayer(*this))); layer.SetInstance(instance, frame); @@ -437,7 +436,7 @@ bool httpCompression, RadiographyLayer::Geometry* geometry) { - RadiographyDicomLayer& layer = dynamic_cast(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this))); + RadiographyDicomLayer& layer = dynamic_cast(RegisterLayer(new RadiographyDicomLayer( *this))); layer.SetInstance(instance, frame); if (geometry != NULL) @@ -451,8 +450,8 @@ orthanc.GetBinaryAsync( uri, headers, - new Callable - (*this, &RadiographyScene::OnTagsReceived), NULL, + new Deprecated::DeprecatedCallable + (GetSharedObserver(), &RadiographyScene::OnTagsReceived), NULL, new Orthanc::SingleValueObject(layer.GetIndex())); } @@ -470,8 +469,8 @@ orthanc.GetBinaryAsync( uri, headers, - new Callable - (*this, &RadiographyScene::OnFrameReceived), NULL, + new Deprecated::DeprecatedCallable + (GetSharedObserver(), &RadiographyScene::OnFrameReceived), NULL, new Orthanc::SingleValueObject(layer.GetIndex())); } @@ -481,7 +480,7 @@ RadiographyLayer& RadiographyScene::LoadDicomWebFrame(Deprecated::IWebService& web) { - RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this)); + RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(*this)); return layer; @@ -857,8 +856,8 @@ orthanc.PostJsonAsyncExpectJson( "/tools/create-dicom", createDicomRequestContent, - new Callable - (*this, &RadiographyScene::OnDicomExported), + new Deprecated::DeprecatedCallable + (GetSharedObserver(), &RadiographyScene::OnDicomExported), NULL, NULL); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyScene.h --- a/Framework/Radiography/RadiographyScene.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyScene.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,6 +22,7 @@ #pragma once #include "RadiographyLayer.h" +#include "../Messages/ObserverBase.h" #include "../Deprecated/Toolbox/DicomFrameConverter.h" #include "../Deprecated/Toolbox/OrthancApiClient.h" #include "../StoneEnumerations.h" @@ -35,8 +36,8 @@ class RadiographyDicomLayer; class RadiographyScene : - public IObserver, - public IObservable + public ObserverBase, + public IObservable { friend class RadiographySceneGeometryReader; public: @@ -193,7 +194,7 @@ virtual void OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message); public: - RadiographyScene(MessageBroker& broker); + RadiographyScene(); virtual ~RadiographyScene(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographySceneReader.cpp --- a/Framework/Radiography/RadiographySceneReader.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographySceneReader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -55,7 +55,7 @@ RadiographyDicomLayer* RadiographySceneGeometryReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry) { - std::unique_ptr layer(new RadiographyPlaceholderLayer(dynamic_cast(scene_).GetBroker(), scene_)); + std::unique_ptr layer(new RadiographyPlaceholderLayer(scene_)); layer->SetGeometry(*geometry); layer->SetSize(dicomImageWidth_, dicomImageHeight_); scene_.RegisterLayer(layer.get()); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographySceneReader.h --- a/Framework/Radiography/RadiographySceneReader.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographySceneReader.h Wed Apr 22 14:05:47 2020 +0200 @@ -37,8 +37,8 @@ class RadiographyPlaceholderLayer : public RadiographyDicomLayer { public: - RadiographyPlaceholderLayer(MessageBroker& broker, const RadiographyScene& scene) : - RadiographyDicomLayer(broker, scene) + RadiographyPlaceholderLayer(const RadiographyScene& scene) : + RadiographyDicomLayer(scene) { } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyTextLayer.cpp --- a/Framework/Radiography/RadiographyTextLayer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyTextLayer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -22,7 +22,7 @@ #include "Core/OrthancException.h" #include "RadiographyScene.h" -#include "Framework/Toolbox/TextRenderer.h" +#include "../Toolbox/TextRenderer.h" namespace OrthancStone { diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyTextLayer.h --- a/Framework/Radiography/RadiographyTextLayer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyTextLayer.h Wed Apr 22 14:05:47 2020 +0200 @@ -37,8 +37,8 @@ static std::map fonts_; public: - RadiographyTextLayer(MessageBroker& broker, const RadiographyScene& scene) : - RadiographyAlphaLayer(broker, scene) + RadiographyTextLayer(const RadiographyScene& scene) : + RadiographyAlphaLayer(scene) { } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyWidget.cpp --- a/Framework/Radiography/RadiographyWidget.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyWidget.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -174,12 +174,9 @@ } - RadiographyWidget::RadiographyWidget(MessageBroker& broker, - boost::shared_ptr scene, + RadiographyWidget::RadiographyWidget(boost::shared_ptr scene, const std::string& name) : WorldSceneWidget(name), - IObserver(broker), - IObservable(broker), invert_(false), interpolation_(ImageInterpolation_Nearest), hasSelection_(false), @@ -271,24 +268,11 @@ void RadiographyWidget::SetScene(boost::shared_ptr scene) { - if (scene_ != NULL) - { - scene_->Unregister(this); - } - scene_ = scene; - scene_->RegisterObserverCallback( - new Callable - (*this, &RadiographyWidget::OnGeometryChanged)); - - scene_->RegisterObserverCallback( - new Callable - (*this, &RadiographyWidget::OnContentChanged)); - - scene_->RegisterObserverCallback( - new Callable - (*this, &RadiographyWidget::OnLayerRemoved)); + Register(*scene_, &RadiographyWidget::OnGeometryChanged); + Register(*scene_, &RadiographyWidget::OnContentChanged); + Register(*scene_, &RadiographyWidget::OnLayerRemoved); Unselect(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Radiography/RadiographyWidget.h --- a/Framework/Radiography/RadiographyWidget.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Radiography/RadiographyWidget.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,6 +22,7 @@ #pragma once #include "../Deprecated/Widgets/WorldSceneWidget.h" +#include "../Messages/ObserverBase.h" #include "RadiographyScene.h" @@ -31,7 +32,7 @@ class RadiographyWidget : public Deprecated::WorldSceneWidget, - public IObserver, + public ObserverBase, public IObservable { public: @@ -64,8 +65,7 @@ bool IsInvertedInternal() const; public: - RadiographyWidget(MessageBroker& broker, - boost::shared_ptr scene, // TODO: check how we can avoid boost::shared_ptr here since we don't want them in the public API (app is keeping a boost::shared_ptr to this right now) + RadiographyWidget(boost::shared_ptr scene, // TODO: check how we can avoid boost::shared_ptr here since we don't want them in the public API (app is keeping a boost::shared_ptr to this right now) const std::string& name); RadiographyScene& GetScene() const diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/CairoCompositor.cpp --- a/Framework/Scene2D/CairoCompositor.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/CairoCompositor.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -85,11 +85,10 @@ } - CairoCompositor::CairoCompositor(const Scene2D& scene, - unsigned int canvasWidth, - unsigned int canvasHeight) : - helper_(scene, *this) + CairoCompositor::CairoCompositor(unsigned int canvasWidth, + unsigned int canvasHeight) { + ResetScene(); UpdateSize(canvasWidth, canvasHeight); } @@ -154,7 +153,7 @@ #endif - void CairoCompositor::Refresh() + void CairoCompositor::Refresh(const Scene2D& scene) { context_.reset(new CairoContext(canvas_)); @@ -162,7 +161,7 @@ cairo_set_source_rgba(context_->GetObject(), 0, 0, 0, 255); cairo_paint(context_->GetObject()); - helper_.Refresh(canvas_.GetWidth(), canvas_.GetHeight()); + helper_->Refresh(scene, canvas_.GetWidth(), canvas_.GetHeight()); context_.reset(); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/CairoCompositor.h --- a/Framework/Scene2D/CairoCompositor.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/CairoCompositor.h Wed Apr 22 14:05:47 2020 +0200 @@ -37,7 +37,7 @@ private: typedef std::map Fonts; - Internals::CompositorHelper helper_; + std::unique_ptr helper_; CairoSurface canvas_; Fonts fonts_; @@ -49,8 +49,7 @@ virtual Internals::CompositorHelper::ILayerRenderer* Create(const ISceneLayer& layer) ORTHANC_OVERRIDE; public: - CairoCompositor(const Scene2D& scene, - unsigned int canvasWidth, + CairoCompositor(unsigned int canvasWidth, unsigned int canvasHeight); virtual ~CairoCompositor(); @@ -80,7 +79,12 @@ Orthanc::Encoding codepage) ORTHANC_OVERRIDE; #endif - virtual void Refresh() ORTHANC_OVERRIDE; + virtual void Refresh(const Scene2D& scene) ORTHANC_OVERRIDE; + + virtual void ResetScene() ORTHANC_OVERRIDE + { + helper_.reset(new Internals::CompositorHelper(*this)); + } void UpdateSize(unsigned int canvasWidth, unsigned int canvasHeight); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/FloatTextureSceneLayer.cpp --- a/Framework/Scene2D/FloatTextureSceneLayer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/FloatTextureSceneLayer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -30,7 +30,8 @@ namespace OrthancStone { FloatTextureSceneLayer::FloatTextureSceneLayer(const Orthanc::ImageAccessor& texture) : - inverted_(false) + inverted_(false), + applyLog_(false) { { std::unique_ptr t( @@ -95,6 +96,14 @@ IncrementRevision(); } + + void FloatTextureSceneLayer::SetApplyLog(bool apply) + { + applyLog_ = apply; + IncrementRevision(); + } + + void FloatTextureSceneLayer::FitRange() { float minValue, maxValue; @@ -126,6 +135,7 @@ cloned->customCenter_ = customCenter_; cloned->customWidth_ = customWidth_; cloned->inverted_ = inverted_; + cloned->applyLog_ = applyLog_; return cloned.release(); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/FloatTextureSceneLayer.h --- a/Framework/Scene2D/FloatTextureSceneLayer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/FloatTextureSceneLayer.h Wed Apr 22 14:05:47 2020 +0200 @@ -32,6 +32,7 @@ float customCenter_; float customWidth_; bool inverted_; + bool applyLog_; public: // The pixel format must be convertible to "Float32" @@ -60,6 +61,13 @@ void FitRange(); + void SetApplyLog(bool apply); + + bool IsApplyLog() const + { + return applyLog_; + } + virtual ISceneLayer* Clone() const; virtual Type GetType() const diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/GrayscaleStyleConfigurator.cpp --- a/Framework/Scene2D/GrayscaleStyleConfigurator.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/GrayscaleStyleConfigurator.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -27,6 +27,18 @@ namespace OrthancStone { + GrayscaleStyleConfigurator::GrayscaleStyleConfigurator() : + revision_(0), + linearInterpolation_(false), + hasWindowingOverride_(false), + customWindowWidth_(0), + customWindowCenter_(0), + hasInversionOverride_(false), + inverted_(false), + applyLog_(false) + { + } + void GrayscaleStyleConfigurator::SetWindowing(ImageWindowing windowing) { hasWindowingOverride_ = true; @@ -60,6 +72,12 @@ revision_++; } + void GrayscaleStyleConfigurator::SetApplyLog(bool apply) + { + applyLog_ = apply; + revision_++; + } + TextureBaseSceneLayer* GrayscaleStyleConfigurator::CreateTextureFromImage( const Orthanc::ImageAccessor& image) const { @@ -100,9 +118,12 @@ l.SetCustomWindowing(customWindowCenter_, customWindowWidth_); } } + if (hasInversionOverride_) { l.SetInverted(inverted_); } + + l.SetApplyLog(applyLog_); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/GrayscaleStyleConfigurator.h --- a/Framework/Scene2D/GrayscaleStyleConfigurator.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/GrayscaleStyleConfigurator.h Wed Apr 22 14:05:47 2020 +0200 @@ -40,18 +40,10 @@ float customWindowCenter_; bool hasInversionOverride_; bool inverted_; + bool applyLog_; public: - GrayscaleStyleConfigurator() : - revision_(0), - linearInterpolation_(false), - hasWindowingOverride_(false), - customWindowWidth_(0), - customWindowCenter_(0), - hasInversionOverride_(false), - inverted_(false) - { - } + GrayscaleStyleConfigurator(); void SetWindowing(ImageWindowing windowing); @@ -68,6 +60,13 @@ return linearInterpolation_; } + void SetApplyLog(bool apply); + + bool IsApplyLog() const + { + return applyLog_; + } + virtual uint64_t GetRevision() const { return revision_; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/ICompositor.h --- a/Framework/Scene2D/ICompositor.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/ICompositor.h Wed Apr 22 14:05:47 2020 +0200 @@ -1,8 +1,9 @@ #pragma once -#include +#include "Scene2D.h" +#include "ScenePoint2D.h" + #include -#include namespace OrthancStone { @@ -17,7 +18,14 @@ virtual unsigned int GetCanvasHeight() const = 0; - virtual void Refresh() = 0; + /** + * WARNING: "Refresh()" must always be called with the same + * scene. If the scene changes, a call to "ResetScene()" must be + * done to reset the tracking of the revisions of the layers. + **/ + virtual void Refresh(const Scene2D& scene) = 0; + + virtual void ResetScene() = 0; #if ORTHANC_ENABLE_LOCALE == 1 virtual void SetFont(size_t index, @@ -25,5 +33,18 @@ unsigned int fontSize, Orthanc::Encoding codepage) = 0; #endif + + // Get the center of the given pixel, in canvas coordinates + ScenePoint2D GetPixelCenterCoordinates(int x, int y) const + { + return ScenePoint2D( + static_cast(x) + 0.5 - static_cast(GetCanvasWidth()) / 2.0, + static_cast(y) + 0.5 - static_cast(GetCanvasHeight()) / 2.0); + } + + void FitContent(Scene2D& scene) const + { + scene.FitContent(GetCanvasWidth(), GetCanvasHeight()); + } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/ISceneLayer.h --- a/Framework/Scene2D/ISceneLayer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/ISceneLayer.h Wed Apr 22 14:05:47 2020 +0200 @@ -33,6 +33,7 @@ public: enum Type { + Type_NullLayer, Type_InfoPanel, Type_ColorTexture, Type_Polyline, diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/InfoPanelSceneLayer.cpp --- a/Framework/Scene2D/InfoPanelSceneLayer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/InfoPanelSceneLayer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -28,10 +28,12 @@ { InfoPanelSceneLayer::InfoPanelSceneLayer(const Orthanc::ImageAccessor& texture, BitmapAnchor anchor, - bool isLinearInterpolation) : + bool isLinearInterpolation, + bool applySceneRotation) : texture_(Orthanc::Image::Clone(texture)), anchor_(anchor), - isLinearInterpolation_(isLinearInterpolation) + isLinearInterpolation_(isLinearInterpolation), + applySceneRotation_(applySceneRotation) { if (texture_->GetFormat() != Orthanc::PixelFormat_RGBA32 && texture_->GetFormat() != Orthanc::PixelFormat_RGB24) diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/InfoPanelSceneLayer.h --- a/Framework/Scene2D/InfoPanelSceneLayer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/InfoPanelSceneLayer.h Wed Apr 22 14:05:47 2020 +0200 @@ -37,15 +37,26 @@ std::unique_ptr texture_; BitmapAnchor anchor_; bool isLinearInterpolation_; + bool applySceneRotation_; public: + /** + * If you supply `true` for `applySceneRotation`, then, in addition to + * a translation to bring it at the desired anchoring location, the image + * will be rotated around its center by the same rotation as the scene + * transformation. + */ InfoPanelSceneLayer(const Orthanc::ImageAccessor& texture, BitmapAnchor anchor, - bool isLinearInterpolation); + bool isLinearInterpolation, + bool applySceneRotation = false); virtual ISceneLayer* Clone() const { - return new InfoPanelSceneLayer(*texture_, anchor_, isLinearInterpolation_); + return new InfoPanelSceneLayer(*texture_, + anchor_, + isLinearInterpolation_, + applySceneRotation_); } const Orthanc::ImageAccessor& GetTexture() const @@ -58,6 +69,11 @@ return anchor_; } + bool ShouldApplySceneRotation() const + { + return applySceneRotation_; + } + bool IsLinearInterpolation() const { return isLinearInterpolation_; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp --- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -53,6 +53,8 @@ target.GetFormat() == Orthanc::PixelFormat_BGRA32 && sizeof(float) == 4); + static const float LOG_NORMALIZATION = 255.0f / log(1.0f + 255.0f); + for (unsigned int y = 0; y < height; y++) { const float* p = reinterpret_cast(source.GetConstRow(y)); @@ -70,6 +72,14 @@ v = 255; } + if (l.IsApplyLog()) + { + // https://theailearner.com/2019/01/01/log-transformation/ + v = LOG_NORMALIZATION * log(1.0f + static_cast(v)); + } + + assert(v >= 0.0f && v <= 255.0f); + uint8_t vv = static_cast(v); if (l.IsInverted()) diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp --- a/Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -34,9 +34,9 @@ texture_.Copy(l.GetTexture(), true); anchor_ = l.GetAnchor(); isLinearInterpolation_ = l.IsLinearInterpolation(); + applySceneRotation_ = l.ShouldApplySceneRotation(); } - void CairoInfoPanelRenderer::Render(const AffineTransform2D& transform, unsigned int canvasWidth, unsigned int canvasHeight) @@ -47,14 +47,71 @@ canvasWidth, canvasHeight); cairo_t* cr = target_.GetCairoContext(); - cairo_save(cr); - cairo_matrix_t t; - cairo_matrix_init_identity(&t); - cairo_matrix_translate(&t, dx, dy); - cairo_transform(cr, &t); + if (applySceneRotation_) + { + // the transformation is as follows: + // - originally, the image is aligned so that its top left corner + // is at 0,0 + // - first, we translate the image by -w/2,-h/2 + // - then we rotate it, so that the next rotation will make the + // image rotate around its center. + // - then, we translate the image by +w/2,+h/2 to put it + // back in place + // - the fourth and last transform is the one that brings the + // image to its desired anchored location. + + int32_t halfWidth = + static_cast(0.5 * texture_.GetWidth()); + + int32_t halfHeight = + static_cast(0.5 * texture_.GetHeight()); + + AffineTransform2D translation1 = + AffineTransform2D::CreateOffset(-halfWidth, -halfHeight); + + const Matrix& sceneTransformM = transform.GetHomogeneousMatrix(); + Matrix r; + Matrix q; + LinearAlgebra::RQDecomposition3x3(r, q, sceneTransformM); + + // first, put the scene rotation in a cairo matrix + cairo_matrix_t m; + cairo_matrix_init( + &m, q(0, 0), q(1, 0), q(0, 1), q(1, 1), q(0, 2), q(1, 2)); + // now let's build the transform piece by piece + // first translation (directly written in `transform`) + cairo_matrix_t transform; + cairo_matrix_init_identity(&transform); + cairo_matrix_translate(&transform, -halfWidth, -halfHeight); + + // then the rotation + cairo_matrix_multiply(&transform, &transform, &m); + + // then the second translation + { + cairo_matrix_t translation2; + cairo_matrix_init_translate(&translation2, halfWidth, halfHeight); + cairo_matrix_multiply(&transform, &transform, &m); + } + + // then the last translation + { + cairo_matrix_t translation3; + cairo_matrix_init_translate(&translation3, dx, dy); + cairo_matrix_multiply(&transform, &transform, &translation3); + } + cairo_transform(cr, &transform); + } + else + { + cairo_matrix_t t; + cairo_matrix_init_identity(&t); + cairo_matrix_translate(&t, dx, dy); + cairo_transform(cr, &t); + } cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_set_source_surface(cr, texture_.GetObject(), 0, 0); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/CairoInfoPanelRenderer.h --- a/Framework/Scene2D/Internals/CairoInfoPanelRenderer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/CairoInfoPanelRenderer.h Wed Apr 22 14:05:47 2020 +0200 @@ -36,11 +36,15 @@ CairoSurface texture_; BitmapAnchor anchor_; bool isLinearInterpolation_; + bool applySceneRotation_; public: CairoInfoPanelRenderer(ICairoContextProvider& target, const ISceneLayer& layer) : - target_(target) + target_(target), + anchor_(BitmapAnchor_TopLeft), + applySceneRotation_(false) + { Update(layer); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp --- a/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -37,18 +37,6 @@ 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(); @@ -56,46 +44,8 @@ Orthanc::ImageAccessor target; texture_.GetWriteableAccessor(target); - - const std::vector& 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(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(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(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; - } - } - + l.Render(target); + cairo_surface_mark_dirty(texture_.GetObject()); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/CompositorHelper.cpp --- a/Framework/Scene2D/Internals/CompositorHelper.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/CompositorHelper.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -80,12 +80,13 @@ }; - void CompositorHelper::Visit(const ISceneLayer& layer, + void CompositorHelper::Visit(const Scene2D& scene, + const ISceneLayer& layer, uint64_t layerIdentifier, int depth) { // "Visit()" is only applied to layers existing in the scene - assert(scene_.HasLayer(depth)); + assert(scene.HasLayer(depth)); Content::iterator found = content_.find(depth); @@ -103,6 +104,8 @@ content_.erase(found); } + // the returned renderer can be NULL in case of an unknown layer + // or a NullLayer std::unique_ptr renderer(factory_.Create(layer)); if (renderer.get() != NULL) @@ -115,7 +118,7 @@ { // This layer has already been rendered assert(found->second->GetLastRevision() <= layer.GetRevision()); - + if (found->second->GetLastRevision() < layer.GetRevision()) { found->second->UpdateRenderer(); @@ -141,18 +144,34 @@ } - void CompositorHelper::Refresh(unsigned int canvasWidth, + void CompositorHelper::Refresh(const Scene2D& scene, + unsigned int canvasWidth, unsigned int canvasHeight) { + /** + * Safeguard mechanism to enforce the fact that the same scene + * is always used with the compositor. Note that the safeguard + * is not 100% bullet-proof, as a new scene might reuse the same + * address as a previous scene. + **/ + if (lastScene_ != NULL && + lastScene_ != &scene) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "ICompositor::ResetScene() should have been called"); + } + + lastScene_ = &scene; + // Bring coordinate (0,0) to the center of the canvas AffineTransform2D offset = AffineTransform2D::CreateOffset( static_cast(canvasWidth) / 2.0, static_cast(canvasHeight) / 2.0); - sceneTransform_ = AffineTransform2D::Combine(offset, scene_.GetSceneToCanvasTransform()); + sceneTransform_ = AffineTransform2D::Combine(offset, scene.GetSceneToCanvasTransform()); canvasWidth_ = canvasWidth; canvasHeight_ = canvasHeight; - scene_.Apply(*this); + scene.Apply(*this); } } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/CompositorHelper.h --- a/Framework/Scene2D/Internals/CompositorHelper.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/CompositorHelper.h Wed Apr 22 14:05:47 2020 +0200 @@ -64,9 +64,9 @@ typedef std::map Content; - const Scene2D& scene_; IRendererFactory& factory_; Content content_; + const Scene2D* lastScene_; // This is only a safeguard, don't use it! // Only valid during a call to Refresh() AffineTransform2D sceneTransform_; @@ -74,21 +74,22 @@ unsigned int canvasHeight_; protected: - virtual void Visit(const ISceneLayer& layer, + virtual void Visit(const Scene2D& scene, + const ISceneLayer& layer, uint64_t layerIdentifier, int depth); public: - CompositorHelper(const Scene2D& scene, - IRendererFactory& factory) : - scene_(scene), - factory_(factory) + CompositorHelper(IRendererFactory& factory) : + factory_(factory), + lastScene_(NULL) { } ~CompositorHelper(); - void Refresh(unsigned int canvasWidth, + void Refresh(const Scene2D& scene, + unsigned int canvasWidth, unsigned int canvasHeight); }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/FixedPointAligner.cpp --- a/Framework/Scene2D/Internals/FixedPointAligner.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/FixedPointAligner.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -25,26 +25,28 @@ { namespace Internals { - FixedPointAligner::FixedPointAligner(boost::weak_ptr controllerW, + FixedPointAligner::FixedPointAligner(boost::shared_ptr viewport, const ScenePoint2D& p) - : controllerW_(controllerW) + : viewport_(viewport) , canvas_(p) { - boost::shared_ptr controller = controllerW_.lock(); - pivot_ = canvas_.Apply(controller->GetCanvasToSceneTransform()); + std::unique_ptr lock(viewport_->Lock()); + pivot_ = canvas_.Apply(lock->GetController().GetCanvasToSceneTransform()); } void FixedPointAligner::Apply() { - boost::shared_ptr controller = controllerW_.lock(); - ScenePoint2D p = canvas_.Apply(controller->GetCanvasToSceneTransform()); + std::unique_ptr lock(viewport_->Lock()); + ScenePoint2D p = canvas_.Apply( + lock->GetController().GetCanvasToSceneTransform()); - controller->SetSceneToCanvasTransform( + lock->GetController().SetSceneToCanvasTransform( AffineTransform2D::Combine( - controller->GetSceneToCanvasTransform(), + lock->GetController().GetSceneToCanvasTransform(), AffineTransform2D::CreateOffset(p.GetX() - pivot_.GetX(), p.GetY() - pivot_.GetY()))); + lock->Invalidate(); } } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/FixedPointAligner.h --- a/Framework/Scene2D/Internals/FixedPointAligner.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/FixedPointAligner.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,6 +22,9 @@ #include "../../Scene2DViewport/PredeclaredTypes.h" #include "../../Scene2D/ScenePoint2D.h" +#include "../../Viewport/IViewport.h" + +#include namespace OrthancStone { @@ -32,12 +35,12 @@ class FixedPointAligner : public boost::noncopyable { private: - boost::weak_ptr controllerW_; + boost::shared_ptr viewport_; ScenePoint2D pivot_; ScenePoint2D canvas_; public: - FixedPointAligner(boost::weak_ptr controllerW, + FixedPointAligner(boost::shared_ptr viewport, const ScenePoint2D& p); void Apply(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp --- a/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,6 +21,8 @@ #include "OpenGLFloatTextureRenderer.h" +#include + namespace OrthancStone { namespace Internals @@ -32,6 +34,11 @@ { if (loadTexture) { + if (layer.IsApplyLog()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + context_.MakeCurrent(); texture_.reset(new OpenGLFloatTextureProgram::Data( context_, layer.GetTexture(), layer.IsLinearInterpolation())); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp --- a/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -32,6 +32,7 @@ context_.MakeCurrent(); texture_.reset(new OpenGL::OpenGLTexture(context_)); texture_->Load(layer.GetTexture(), layer.IsLinearInterpolation()); + applySceneRotation_ = layer.ShouldApplySceneRotation(); anchor_ = layer.GetAnchor(); } } @@ -41,11 +42,11 @@ const InfoPanelSceneLayer& layer) : context_(context), program_(program), - anchor_(BitmapAnchor_TopLeft) + anchor_(BitmapAnchor_TopLeft), + applySceneRotation_(false) { LoadTexture(layer); } - void OpenGLInfoPanelRenderer::Render(const AffineTransform2D& transform, unsigned int canvasWidth, @@ -60,7 +61,56 @@ // The position of this type of layer is layer: Ignore the // "transform" coming from the scene - program_.Apply(*texture_, AffineTransform2D::CreateOffset(dx, dy), true); + AffineTransform2D actualTransform = + AffineTransform2D::CreateOffset(dx, dy); + + if (applySceneRotation_) + { + // the transformation is as follows: + // - originally, the image is aligned so that its top left corner + // is at 0,0 + // - first, we translate the image by -w/2,-h/2 + // - then we rotate it, so that the next rotation will make the + // image rotate around its center. + // - then, we translate the image by +w/2,+h/2 to put it + // back in place + // - the fourth and last transform is the one that brings the + // image to its desired anchored location. + + int32_t halfWidth = + static_cast(0.5 * texture_->GetWidth()); + + int32_t halfHeight= + static_cast(0.5 * texture_->GetHeight()); + + AffineTransform2D translation1 = + AffineTransform2D::CreateOffset(-halfWidth, -halfHeight); + + const Matrix& sceneTransformM = transform.GetHomogeneousMatrix(); + Matrix r; + Matrix q; + LinearAlgebra::RQDecomposition3x3(r, q, sceneTransformM); + + // counterintuitively, q is the rotation and r is the upper + // triangular + AffineTransform2D rotation(q); + + AffineTransform2D translation2 = + AffineTransform2D::CreateOffset(halfWidth, halfHeight); + + // please note that the last argument is the 1st applied + // transformation (rationale: if arguments are a, b and c, then + // the resulting matrix is a*b*c: + // x2 = (a*b*c)*x1 = (a*(b*(c*x1))) (you can see that the result + // of c*x1 is transformed by b, and the result of b*c*x1 is trans- + // formed by a) + actualTransform = AffineTransform2D::Combine(actualTransform, + translation2, + rotation, + translation1); + } + + program_.Apply(*texture_, actualTransform, true); } } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.h --- a/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.h Wed Apr 22 14:05:47 2020 +0200 @@ -36,6 +36,7 @@ OpenGLColorTextureProgram& program_; std::unique_ptr texture_; BitmapAnchor anchor_; + bool applySceneRotation_; void LoadTexture(const InfoPanelSceneLayer& layer); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp --- a/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -39,76 +39,14 @@ const unsigned int width = source.GetWidth(); const unsigned int height = source.GetHeight(); - if ((texture_.get() == NULL) || - (texture_->GetWidth() != width) || - (texture_->GetHeight() != height)) + if (texture_.get() == NULL || + texture_->GetWidth() != width || + texture_->GetHeight() != height) { - - texture_.reset(new Orthanc::Image( - Orthanc::PixelFormat_RGBA32, - width, - height, - false)); + 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& 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(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(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(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; - } - } - - } + layer.Render(*texture_); context_.MakeCurrent(); glTexture_.reset(new OpenGL::OpenGLTexture(context_)); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Internals/OpenGLTextureProgram.h diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/LookupTableStyleConfigurator.cpp --- a/Framework/Scene2D/LookupTableStyleConfigurator.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/LookupTableStyleConfigurator.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -39,7 +39,8 @@ LookupTableStyleConfigurator::LookupTableStyleConfigurator() : revision_(0), hasLut_(false), - hasRange_(false) + hasRange_(false), + applyLog_(false) { } @@ -82,6 +83,12 @@ } } + void LookupTableStyleConfigurator::SetApplyLog(bool apply) + { + applyLog_ = apply; + revision_++; + } + TextureBaseSceneLayer* LookupTableStyleConfigurator::CreateTextureFromImage(const Orthanc::ImageAccessor& image) const { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); @@ -104,5 +111,7 @@ { l.FitRange(); } + + l.SetApplyLog(applyLog_); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/LookupTableStyleConfigurator.h --- a/Framework/Scene2D/LookupTableStyleConfigurator.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/LookupTableStyleConfigurator.h Wed Apr 22 14:05:47 2020 +0200 @@ -39,6 +39,7 @@ bool hasRange_; float minValue_; float maxValue_; + bool applyLog_; public: LookupTableStyleConfigurator(); @@ -55,6 +56,13 @@ void SetRange(float minValue, float maxValue); + void SetApplyLog(bool apply); + + bool IsApplyLog() const + { + return applyLog_; + } + virtual uint64_t GetRevision() const { return revision_; @@ -63,7 +71,7 @@ virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const; virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame, - const DicomInstanceParameters& parameters) const + const DicomInstanceParameters& parameters) const { return parameters.CreateLookupTableTexture(frame); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/LookupTableTextureSceneLayer.cpp --- a/Framework/Scene2D/LookupTableTextureSceneLayer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/LookupTableTextureSceneLayer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -27,7 +27,8 @@ namespace OrthancStone { - LookupTableTextureSceneLayer::LookupTableTextureSceneLayer(const Orthanc::ImageAccessor& texture) + LookupTableTextureSceneLayer::LookupTableTextureSceneLayer(const Orthanc::ImageAccessor& texture) : + applyLog_(false) { { std::unique_ptr t( @@ -132,6 +133,12 @@ } } + void LookupTableTextureSceneLayer::SetApplyLog(bool apply) + { + applyLog_ = apply; + IncrementRevision(); + } + void LookupTableTextureSceneLayer::FitRange() { Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue_, maxValue_, GetTexture()); @@ -158,4 +165,147 @@ return cloned.release(); } + + + // Templatized function to speed up computations, by avoiding + // testing conditions on each pixel + template + static void RenderInternal(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + float minValue, + float slope, + const std::vector& lut) + { + static const float LOG_NORMALIZATION = 255.0f / log(1.0f + 255.0f); + + const unsigned int width = source.GetWidth(); + const unsigned int height = source.GetHeight(); + + for (unsigned int y = 0; y < height; y++) + { + const float* p = reinterpret_cast(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast(target.GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + float v = (*p - minValue) * slope; + if (v <= 0) + { + v = 0; + } + else if (v >= 255) + { + v = 255; + } + + if (IsApplyLog) + { + // https://theailearner.com/2019/01/01/log-transformation/ + v = LOG_NORMALIZATION * log(1.0f + static_cast(v)); + } + + assert(v >= 0.0f && v <= 255.0f); + + uint8_t vv = static_cast(v); + + switch (TargetFormat) + { + case Orthanc::PixelFormat_BGRA32: + // For Cairo surfaces + 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 + break; + + case Orthanc::PixelFormat_RGBA32: + // For OpenGL + 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 + break; + + default: + assert(0); + } + + p++; + q += 4; + } + } + } + + + void LookupTableTextureSceneLayer::Render(Orthanc::ImageAccessor& target) const + { + assert(sizeof(float) == 4); + + if (!HasTexture()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + const Orthanc::ImageAccessor& source = GetTexture(); + + if (source.GetFormat() != Orthanc::PixelFormat_Float32 || + (target.GetFormat() != Orthanc::PixelFormat_RGBA32 && + target.GetFormat() != Orthanc::PixelFormat_BGRA32)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (source.GetWidth() != target.GetWidth() || + source.GetHeight() != target.GetHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + const float minValue = GetMinValue(); + float slope; + + if (GetMinValue() >= GetMaxValue()) + { + slope = 0; + } + else + { + slope = 256.0f / (GetMaxValue() - GetMinValue()); + } + + const std::vector& lut = GetLookupTable(); + if (lut.size() != 4 * 256) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + switch (target.GetFormat()) + { + case Orthanc::PixelFormat_RGBA32: + if (applyLog_) + { + RenderInternal(target, source, minValue, slope, lut); + } + else + { + RenderInternal(target, source, minValue, slope, lut); + } + break; + + case Orthanc::PixelFormat_BGRA32: + if (applyLog_) + { + RenderInternal(target, source, minValue, slope, lut); + } + else + { + RenderInternal(target, source, minValue, slope, lut); + } + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/LookupTableTextureSceneLayer.h --- a/Framework/Scene2D/LookupTableTextureSceneLayer.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/LookupTableTextureSceneLayer.h Wed Apr 22 14:05:47 2020 +0200 @@ -32,6 +32,7 @@ float minValue_; float maxValue_; std::vector lut_; + bool applyLog_; void SetLookupTableRgb(const std::vector& lut); @@ -66,11 +67,22 @@ return lut_; } + void SetApplyLog(bool apply); + + bool IsApplyLog() const + { + return applyLog_; + } + virtual ISceneLayer* Clone() const; virtual Type GetType() const { return Type_LookupTableTexture; } + + // Render the texture to a color image of format BGRA32 (Cairo + // surfaces) or RGBA32 (OpenGL) + void Render(Orthanc::ImageAccessor& target) const; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/NullLayer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Scene2D/NullLayer.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include "ISceneLayer.h" + +#include + +#include + +/** + This layer can be used when a z-index needs to be booked inside a Scene2D. + + It can later be replaced by the actual layer. +*/ +namespace OrthancStone +{ + class NullLayer : public ISceneLayer + { + public: + NullLayer() {} + + virtual ISceneLayer* Clone() const ORTHANC_OVERRIDE + { + return new NullLayer(); + } + + virtual Type GetType() const ORTHANC_OVERRIDE + { + return Type_NullLayer; + } + + virtual bool GetBoundingBox(Extent2D& target) const ORTHANC_OVERRIDE + { + target = Extent2D(); + return false; + } + + virtual uint64_t GetRevision() const ORTHANC_OVERRIDE + { + return 0; + } + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/OpenGLCompositor.cpp --- a/Framework/Scene2D/OpenGLCompositor.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/OpenGLCompositor.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **/ @@ -128,10 +128,8 @@ } } - OpenGLCompositor::OpenGLCompositor(OpenGL::IOpenGLContext& context, - const Scene2D& scene) : + OpenGLCompositor::OpenGLCompositor(OpenGL::IOpenGLContext& context) : context_(context), - helper_(scene, *this), colorTextureProgram_(context), floatTextureProgram_(context), linesProgram_(context), @@ -139,22 +137,52 @@ canvasWidth_(0), canvasHeight_(0) { + if (!context_.IsContextLost()) + { + canvasWidth_ = context_.GetCanvasWidth(); + canvasHeight_ = context_.GetCanvasHeight(); + } + + ResetScene(); } OpenGLCompositor::~OpenGLCompositor() { if (!context_.IsContextLost()) { - context_.MakeCurrent(); // this can throw if context lost! - for (Fonts::iterator it = fonts_.begin(); it != fonts_.end(); ++it) + try { - assert(it->second != NULL); - delete it->second; + try + { + context_.MakeCurrent(); // this can throw if context lost! + } + catch (...) + { + LOG(ERROR) << "context_.MakeCurrent() failed in OpenGLCompositor::~OpenGLCompositor()!"; + } + + for (Fonts::iterator it = fonts_.begin(); it != fonts_.end(); ++it) + { + try + { + + assert(it->second != NULL); + delete it->second; + } + catch (...) + { + LOG(ERROR) << "Exception thrown while deleting OpenGL-based font!"; + } + } + } + catch (...) + { + // logging threw an exception! } } } - void OpenGLCompositor::Refresh() + void OpenGLCompositor::Refresh(const Scene2D& scene) { if (!context_.IsContextLost()) { @@ -167,11 +195,10 @@ glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); - helper_.Refresh(canvasWidth_, canvasHeight_); + helper_->Refresh(scene, canvasWidth_, canvasHeight_); context_.SwapBuffer(); } - } void OpenGLCompositor::SetFont(size_t index, diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/OpenGLCompositor.h --- a/Framework/Scene2D/OpenGLCompositor.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/OpenGLCompositor.h Wed Apr 22 14:05:47 2020 +0200 @@ -37,27 +37,31 @@ typedef std::map Fonts; - OpenGL::IOpenGLContext& context_; - Fonts fonts_; - Internals::CompositorHelper helper_; - Internals::OpenGLColorTextureProgram colorTextureProgram_; - Internals::OpenGLFloatTextureProgram floatTextureProgram_; - Internals::OpenGLLinesProgram linesProgram_; - Internals::OpenGLTextProgram textProgram_; - unsigned int canvasWidth_; - unsigned int canvasHeight_; + OpenGL::IOpenGLContext& context_; + Fonts fonts_; + std::unique_ptr helper_; + Internals::OpenGLColorTextureProgram colorTextureProgram_; + Internals::OpenGLFloatTextureProgram floatTextureProgram_; + Internals::OpenGLLinesProgram linesProgram_; + Internals::OpenGLTextProgram textProgram_; + unsigned int canvasWidth_; + unsigned int canvasHeight_; const Font* GetFont(size_t fontIndex) const; virtual Internals::CompositorHelper::ILayerRenderer* Create(const ISceneLayer& layer) ORTHANC_OVERRIDE; public: - OpenGLCompositor(OpenGL::IOpenGLContext& context, - const Scene2D& scene); + OpenGLCompositor(OpenGL::IOpenGLContext& context); virtual ~OpenGLCompositor(); - virtual void Refresh() ORTHANC_OVERRIDE; + virtual void Refresh(const Scene2D& scene) ORTHANC_OVERRIDE; + + virtual void ResetScene() ORTHANC_OVERRIDE + { + helper_.reset(new Internals::CompositorHelper(*this)); + } void SetFont(size_t index, const GlyphBitmapAlphabet& dict); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/PanSceneTracker.cpp --- a/Framework/Scene2D/PanSceneTracker.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/PanSceneTracker.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -20,16 +20,23 @@ #include "PanSceneTracker.h" +#include "../Viewport/IViewport.h" #include "../Scene2DViewport/ViewportController.h" +#include + namespace OrthancStone { - PanSceneTracker::PanSceneTracker(boost::weak_ptr controllerW, + PanSceneTracker::PanSceneTracker(boost::shared_ptr viewport, const PointerEvent& event) - : OneGesturePointerTracker(controllerW) - , originalSceneToCanvas_(GetController()->GetSceneToCanvasTransform()) - , originalCanvasToScene_(GetController()->GetCanvasToSceneTransform()) + : OneGesturePointerTracker(viewport) { + + std::unique_ptr lock(viewport_->Lock()); + + originalSceneToCanvas_ = lock->GetController().GetSceneToCanvasTransform(); + originalCanvasToScene_ = lock->GetController().GetCanvasToSceneTransform(); + pivot_ = event.GetMainPosition().Apply(originalCanvasToScene_); } @@ -38,25 +45,19 @@ { ScenePoint2D p = event.GetMainPosition().Apply(originalCanvasToScene_); - // The controller is a weak pointer. It could be deleted when the - // tracker is still alive (for instance, because of a lost WebGL - // context) - if(GetController().get() != NULL) - { - GetController()->SetSceneToCanvasTransform( - AffineTransform2D::Combine( - originalSceneToCanvas_, - AffineTransform2D::CreateOffset(p.GetX() - pivot_.GetX(), - p.GetY() - pivot_.GetY()))); - } + std::unique_ptr lock(viewport_->Lock()); + + lock->GetController().SetSceneToCanvasTransform( + AffineTransform2D::Combine( + originalSceneToCanvas_, + AffineTransform2D::CreateOffset(p.GetX() - pivot_.GetX(), + p.GetY() - pivot_.GetY()))); + lock->Invalidate(); } void PanSceneTracker::Cancel() { - if(GetController().get() != NULL) - { - GetController()->SetSceneToCanvasTransform(originalSceneToCanvas_); - } + std::unique_ptr lock(viewport_->Lock()); + lock->GetController().SetSceneToCanvasTransform(originalSceneToCanvas_); } - } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/PanSceneTracker.h --- a/Framework/Scene2D/PanSceneTracker.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/PanSceneTracker.h Wed Apr 22 14:05:47 2020 +0200 @@ -28,14 +28,13 @@ class PanSceneTracker : public OneGesturePointerTracker { public: - PanSceneTracker(boost::weak_ptr controllerW, + PanSceneTracker(boost::shared_ptr viewport, const PointerEvent& event); virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE; virtual void Cancel() ORTHANC_OVERRIDE; private: - boost::weak_ptr controllerW_; ScenePoint2D pivot_; AffineTransform2D originalSceneToCanvas_; AffineTransform2D originalCanvasToScene_; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/PointerEvent.cpp --- a/Framework/Scene2D/PointerEvent.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/PointerEvent.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -26,6 +26,7 @@ namespace OrthancStone { PointerEvent::PointerEvent() : + button_(MouseButton_None), hasAltModifier_(false), hasControlModifier_(false), hasShiftModifier_(false) diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/PointerEvent.h --- a/Framework/Scene2D/PointerEvent.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/PointerEvent.h Wed Apr 22 14:05:47 2020 +0200 @@ -31,6 +31,7 @@ class PointerEvent : public boost::noncopyable { private: + MouseButton button_; std::vector positions_; bool hasAltModifier_; bool hasControlModifier_; @@ -88,5 +89,15 @@ { return hasShiftModifier_; } + + void SetMouseButton(MouseButton button) + { + button_ = button; + } + + MouseButton GetMouseButton() const + { + return button_; + } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/RotateSceneTracker.cpp --- a/Framework/Scene2D/RotateSceneTracker.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/RotateSceneTracker.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **/ @@ -23,23 +23,25 @@ namespace OrthancStone { - RotateSceneTracker::RotateSceneTracker(boost::weak_ptr controllerW, + RotateSceneTracker::RotateSceneTracker(boost::shared_ptr viewport, const PointerEvent& event) - : OneGesturePointerTracker(controllerW) + : OneGesturePointerTracker(viewport) , click_(event.GetMainPosition()) - , aligner_(controllerW, click_) + , aligner_(viewport, click_) , isFirst_(true) - , originalSceneToCanvas_(GetController()->GetSceneToCanvasTransform()) { + std::unique_ptr lock(viewport_->Lock()); + originalSceneToCanvas_ = lock->GetController().GetSceneToCanvasTransform(); + } - + void RotateSceneTracker::PointerMove(const PointerEvent& event) { ScenePoint2D p = event.GetMainPosition(); double dx = p.GetX() - click_.GetX(); double dy = p.GetY() - click_.GetY(); - if (std::abs(dx) > 5.0 || + if (std::abs(dx) > 5.0 || std::abs(dy) > 5.0) { double a = atan2(dy, dx); @@ -50,28 +52,22 @@ isFirst_ = false; } - // The controller is a weak pointer. It could be deleted when the - // tracker is still alive (for instance, because of a lost WebGL - // context that triggers a recreation of the viewport) - if(GetController().get() != NULL) - { - GetController()->SetSceneToCanvasTransform( - AffineTransform2D::Combine( - AffineTransform2D::CreateRotation(a - referenceAngle_), - originalSceneToCanvas_)); - - aligner_.Apply(); - } + std::unique_ptr lock(viewport_->Lock()); + + lock->GetController().SetSceneToCanvasTransform( + AffineTransform2D::Combine( + AffineTransform2D::CreateRotation(a - referenceAngle_), + originalSceneToCanvas_)); + aligner_.Apply(); + lock->Invalidate(); } } void RotateSceneTracker::Cancel() { - // See remark above - if(GetController().get() != NULL) - { - GetController()->SetSceneToCanvasTransform(originalSceneToCanvas_); - } + // See remark above + std::unique_ptr lock(viewport_->Lock()); + lock->GetController().SetSceneToCanvasTransform(originalSceneToCanvas_); + lock->Invalidate(); } - } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/RotateSceneTracker.h --- a/Framework/Scene2D/RotateSceneTracker.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/RotateSceneTracker.h Wed Apr 22 14:05:47 2020 +0200 @@ -30,7 +30,7 @@ class RotateSceneTracker : public OneGesturePointerTracker { public: - RotateSceneTracker(boost::weak_ptr controllerW, + RotateSceneTracker(boost::shared_ptr viewport, const PointerEvent& event); virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Scene2D.cpp --- a/Framework/Scene2D/Scene2D.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Scene2D.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -208,7 +208,7 @@ it != content_.end(); ++it) { assert(it->second != NULL); - visitor.Visit(it->second->GetLayer(), it->second->GetIdentifier(), it->first); + visitor.Visit(*this, it->second->GetLayer(), it->second->GetIdentifier(), it->first); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/Scene2D.h --- a/Framework/Scene2D/Scene2D.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/Scene2D.h Wed Apr 22 14:05:47 2020 +0200 @@ -40,7 +40,8 @@ { } - virtual void Visit(const ISceneLayer& layer, + virtual void Visit(const Scene2D& scene, + const ISceneLayer& layer, uint64_t layerIdentifier, int depth) = 0; }; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/ScenePoint2D.h --- a/Framework/Scene2D/ScenePoint2D.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/ScenePoint2D.h Wed Apr 22 14:05:47 2020 +0200 @@ -64,115 +64,115 @@ return ScenePoint2D(x, y); } - const ScenePoint2D operator-(const ScenePoint2D& a) const - { - ScenePoint2D v; - v.x_ = x_ - a.x_; - v.y_ = y_ - a.y_; - - return v; - } - - const ScenePoint2D operator+(const ScenePoint2D& a) const - { - ScenePoint2D v; - v.x_ = x_ + a.x_; - v.y_ = y_ + a.y_; - - return v; - } - - const ScenePoint2D operator*(double a) const - { - ScenePoint2D v; - v.x_ = x_ * a; - v.y_ = y_ * a; - - return v; - } - - const ScenePoint2D operator/(double a) const - { - ScenePoint2D v; - v.x_ = x_ / a; - v.y_ = y_ / a; - - return v; - } - - static void MidPoint(ScenePoint2D& result, const ScenePoint2D& a, const ScenePoint2D& b) - { - result.x_ = 0.5 * (a.x_ + b.x_); - result.y_ = 0.5 * (a.y_ + b.y_); - } - - static double Dot(const ScenePoint2D& a, const ScenePoint2D& b) - { - return a.x_ * b.x_ + a.y_ * b.y_; - } + const ScenePoint2D operator-(const ScenePoint2D& a) const + { + ScenePoint2D v; + v.x_ = x_ - a.x_; + v.y_ = y_ - a.y_; + + return v; + } + + const ScenePoint2D operator+(const ScenePoint2D& a) const + { + ScenePoint2D v; + v.x_ = x_ + a.x_; + v.y_ = y_ + a.y_; + + return v; + } + + const ScenePoint2D operator*(double a) const + { + ScenePoint2D v; + v.x_ = x_ * a; + v.y_ = y_ * a; - static double SquaredMagnitude(const ScenePoint2D& v) - { - return v.x_ * v.x_ + v.y_ * v.y_; - } + return v; + } + + const ScenePoint2D operator/(double a) const + { + ScenePoint2D v; + v.x_ = x_ / a; + v.y_ = y_ / a; + + return v; + } + + static void MidPoint(ScenePoint2D& result, const ScenePoint2D& a, const ScenePoint2D& b) + { + result.x_ = 0.5 * (a.x_ + b.x_); + result.y_ = 0.5 * (a.y_ + b.y_); + } + + static double Dot(const ScenePoint2D& a, const ScenePoint2D& b) + { + return a.x_ * b.x_ + a.y_ * b.y_; + } + + static double SquaredMagnitude(const ScenePoint2D& v) + { + return v.x_ * v.x_ + v.y_ * v.y_; + } - static double Magnitude(const ScenePoint2D& v) - { - double squaredMagnitude = SquaredMagnitude(v); - if (LinearAlgebra::IsCloseToZero(squaredMagnitude)) - return 0.0; - return sqrt(squaredMagnitude); - } + static double Magnitude(const ScenePoint2D& v) + { + double squaredMagnitude = SquaredMagnitude(v); + if (LinearAlgebra::IsCloseToZero(squaredMagnitude)) + return 0.0; + return sqrt(squaredMagnitude); + } - static double SquaredDistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b) - { - ScenePoint2D n = b - a; - return Dot(n, n); - } + static double SquaredDistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b) + { + ScenePoint2D n = b - a; + return Dot(n, n); + } - static double DistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b) - { - double squaredDist = SquaredDistancePtPt(a, b); - return sqrt(squaredDist); - } + static double DistancePtPt(const ScenePoint2D& a, const ScenePoint2D& b) + { + double squaredDist = SquaredDistancePtPt(a, b); + return sqrt(squaredDist); + } + + /** + Distance from point p to [a,b] segment - /** - Distance from point p to [a,b] segment - - Rewritten from https://www.randygaul.net/2014/07/23/distance-point-to-line-segment/ - */ - static double SquaredDistancePtSegment(const ScenePoint2D& a, const ScenePoint2D& b, const ScenePoint2D& p) - { - ScenePoint2D n = b - a; - ScenePoint2D pa = a - p; - - double c = Dot(n, pa); - - // Closest point is a - if (c > 0.0) - return Dot(pa, pa); - - ScenePoint2D bp = p - b; - - // Closest point is b - if (Dot(n, bp) > 0.0) - return Dot(bp, bp); - - // if segment length is very short, we approximate distance to the - // distance with a - double nq = Dot(n, n); - if (LinearAlgebra::IsCloseToZero(nq)) - { - // segment is very small: approximate distance from point to segment - // with distance from p to a - return Dot(pa, pa); - } - else - { - // Closest point is between a and b - ScenePoint2D e = pa - n * (c / nq); - return Dot(e, e); - } + Rewritten from https://www.randygaul.net/2014/07/23/distance-point-to-line-segment/ + */ + static double SquaredDistancePtSegment(const ScenePoint2D& a, const ScenePoint2D& b, const ScenePoint2D& p) + { + ScenePoint2D n = b - a; + ScenePoint2D pa = a - p; + + double c = Dot(n, pa); + + // Closest point is a + if (c > 0.0) + return Dot(pa, pa); + + ScenePoint2D bp = p - b; + + // Closest point is b + if (Dot(n, bp) > 0.0) + return Dot(bp, bp); + + // if segment length is very short, we approximate distance to the + // distance with a + double nq = Dot(n, n); + if (LinearAlgebra::IsCloseToZero(nq)) + { + // segment is very small: approximate distance from point to segment + // with distance from p to a + return Dot(pa, pa); + } + else + { + // Closest point is between a and b + ScenePoint2D e = pa - n * (c / nq); + return Dot(e, e); + } } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/ZoomSceneTracker.cpp --- a/Framework/Scene2D/ZoomSceneTracker.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/ZoomSceneTracker.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -22,19 +22,19 @@ #include "ZoomSceneTracker.h" #include "../Scene2DViewport/ViewportController.h" -using boost::weak_ptr; -using boost::shared_ptr; - namespace OrthancStone { - ZoomSceneTracker::ZoomSceneTracker(weak_ptr controllerW, + ZoomSceneTracker::ZoomSceneTracker(boost::shared_ptr viewport, const PointerEvent& event, unsigned int canvasHeight) - : OneGesturePointerTracker(controllerW) + : OneGesturePointerTracker(viewport) , clickY_(event.GetMainPosition().GetY()) - , aligner_(controllerW, event.GetMainPosition()) - , originalSceneToCanvas_(GetController()->GetSceneToCanvasTransform()) + , aligner_(viewport, event.GetMainPosition()) { + + std::unique_ptr lock(viewport_->Lock()); + originalSceneToCanvas_ = lock->GetController().GetSceneToCanvasTransform(); + if (canvasHeight <= 3) { active_ = false; @@ -76,26 +76,20 @@ double zoom = pow(2.0, z); - // The controller is a weak pointer. It could be deleted when the - // tracker is still alive (for instance, because of a lost WebGL - // context) - if(GetController().get() != NULL) - { - GetController()->SetSceneToCanvasTransform( - AffineTransform2D::Combine( - AffineTransform2D::CreateScaling(zoom, zoom), - originalSceneToCanvas_)); - - aligner_.Apply(); - } + std::unique_ptr lock(viewport_->Lock()); + lock->GetController().SetSceneToCanvasTransform( + AffineTransform2D::Combine( + AffineTransform2D::CreateScaling(zoom, zoom), + originalSceneToCanvas_)); + aligner_.Apply(); + lock->Invalidate(); } } void ZoomSceneTracker::Cancel() { - if(GetController().get() != NULL) - { - GetController()->SetSceneToCanvasTransform(originalSceneToCanvas_); - } + std::unique_ptr lock(viewport_->Lock()); + lock->GetController().SetSceneToCanvasTransform(originalSceneToCanvas_); + lock->Invalidate(); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2D/ZoomSceneTracker.h --- a/Framework/Scene2D/ZoomSceneTracker.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2D/ZoomSceneTracker.h Wed Apr 22 14:05:47 2020 +0200 @@ -23,14 +23,17 @@ #include "../Scene2DViewport/OneGesturePointerTracker.h" +#include "../Viewport/IViewport.h" #include "Internals/FixedPointAligner.h" +#include + namespace OrthancStone { class ZoomSceneTracker : public OneGesturePointerTracker { public: - ZoomSceneTracker(boost::weak_ptr controllerW, + ZoomSceneTracker(boost::shared_ptr viewport, const PointerEvent& event, unsigned int canvasHeight); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/AngleMeasureTool.cpp --- a/Framework/Scene2DViewport/AngleMeasureTool.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -42,16 +42,23 @@ // the params in the LayerHolder ctor specify the number of polyline and text // layers AngleMeasureTool::AngleMeasureTool( - MessageBroker& broker, boost::weak_ptr controllerW) - : MeasureTool(broker, controllerW) + boost::shared_ptr viewport) + : MeasureTool(viewport) #if ORTHANC_STONE_ENABLE_OUTLINED_TEXT == 1 - , layerHolder_(boost::make_shared(controllerW,1,5)) + , layerHolder_(boost::shared_ptr(new LayerHolder(viewport,1,5))) #else - , layerHolder_(boost::make_shared(controllerW, 1, 1)) + , layerHolder_(boost::shared_ptr(new LayerHolder(viewport,1,1))) #endif , angleHighlightArea_(AngleHighlightArea_None) { - RefreshScene(); + } + + boost::shared_ptr AngleMeasureTool::Create(boost::shared_ptr viewport) + { + boost::shared_ptr obj(new AngleMeasureTool(viewport)); + obj->MeasureTool::PostConstructor(); + obj->RefreshScene(); + return obj; } AngleMeasureTool::~AngleMeasureTool() @@ -108,7 +115,9 @@ void AngleMeasureTool::SetMemento(boost::shared_ptr mementoBase) { - boost::shared_ptr memento = boost::dynamic_pointer_cast(mementoBase); + boost::shared_ptr memento = + boost::dynamic_pointer_cast(mementoBase); + ORTHANC_ASSERT(memento.get() != NULL, "Internal error: wrong (or bad) memento"); center_ = memento->center_; side1End_ = memento->side1End_; @@ -119,7 +128,8 @@ std::string AngleMeasureTool::GetDescription() { std::stringstream ss; - ss << "AngleMeasureTool. Center = " << center_ << " Side1End = " << side1End_ << " Side2End = " << side2End_; + ss << "AngleMeasureTool. Center = " << center_ << " Side1End = " + << side1End_ << " Side2End = " << side2End_; return ss.str(); } @@ -131,36 +141,51 @@ AngleMeasureTool::AngleHighlightArea AngleMeasureTool::AngleHitTest(ScenePoint2D p) const { - const double pixelToScene = - GetController()->GetScene().GetCanvasToSceneTransform().ComputeZoom(); - const double SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD = pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD * pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD; + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + + const double pixelToScene = scene.GetCanvasToSceneTransform().ComputeZoom(); + + const double SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD = + pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD * + pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD; { - const double sqDistanceFromSide1End = ScenePoint2D::SquaredDistancePtPt(p, side1End_); + const double sqDistanceFromSide1End = + ScenePoint2D::SquaredDistancePtPt(p, side1End_); + if (sqDistanceFromSide1End <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD) return AngleHighlightArea_Side1End; } { - const double sqDistanceFromSide2End = ScenePoint2D::SquaredDistancePtPt(p, side2End_); + const double sqDistanceFromSide2End = + ScenePoint2D::SquaredDistancePtPt(p, side2End_); + if (sqDistanceFromSide2End <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD) return AngleHighlightArea_Side2End; } { - const double sqDistanceFromCenter = ScenePoint2D::SquaredDistancePtPt(p, center_); + const double sqDistanceFromCenter = + ScenePoint2D::SquaredDistancePtPt(p, center_); if (sqDistanceFromCenter <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD) return AngleHighlightArea_Center; } { - const double sqDistanceFromSide1 = ScenePoint2D::SquaredDistancePtSegment(center_, side1End_, p); + const double sqDistanceFromSide1 = + ScenePoint2D::SquaredDistancePtSegment(center_, side1End_, p); + if (sqDistanceFromSide1 <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD) return AngleHighlightArea_Side1; } { - const double sqDistanceFromSide2 = ScenePoint2D::SquaredDistancePtSegment(center_, side2End_, p); + const double sqDistanceFromSide2 = + ScenePoint2D::SquaredDistancePtSegment(center_, side2End_, p); + if (sqDistanceFromSide2 <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD) return AngleHighlightArea_Side2; } @@ -168,7 +193,7 @@ return AngleHighlightArea_None; } - bool AngleMeasureTool::HitTest(ScenePoint2D p) const + bool AngleMeasureTool::HitTest(ScenePoint2D p) { return AngleHitTest(p) != AngleHighlightArea_None; } @@ -176,8 +201,12 @@ boost::shared_ptr AngleMeasureTool::CreateEditionTracker(const PointerEvent& e) { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + ScenePoint2D scenePos = e.GetMainPosition().Apply( - GetController()->GetScene().GetCanvasToSceneTransform()); + scene.GetCanvasToSceneTransform()); if (!HitTest(scenePos)) return boost::shared_ptr(); @@ -186,11 +215,12 @@ new EditLineMeasureTracker( boost::shared_ptr measureTool; MessageBroker & broker, - boost::weak_ptr controllerW, + boost::shared_ptr viewport, const PointerEvent & e); */ + boost::shared_ptr editAngleMeasureTracker( - new EditAngleMeasureTracker(shared_from_this(), GetBroker(), GetController(), e)); + new EditAngleMeasureTracker(shared_from_this(), viewport_, e)); return editAngleMeasureTracker; } @@ -204,7 +234,10 @@ { if (IsSceneAlive()) { - boost::shared_ptr controller = GetController(); + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + if (IsEnabled()) { layerHolder_->CreateLayersIfNeeded(); @@ -212,72 +245,90 @@ { // Fill the polyline layer with the measurement lines PolylineSceneLayer* polylineLayer = layerHolder_->GetPolylineLayer(0); - polylineLayer->ClearAllChains(); + if (polylineLayer) + { + polylineLayer->ClearAllChains(); + + const Color color(TOOL_ANGLE_LINES_COLOR_RED, + TOOL_ANGLE_LINES_COLOR_GREEN, + TOOL_ANGLE_LINES_COLOR_BLUE); + + const Color highlightColor(TOOL_ANGLE_LINES_HL_COLOR_RED, + TOOL_ANGLE_LINES_HL_COLOR_GREEN, + TOOL_ANGLE_LINES_HL_COLOR_BLUE); + + // sides + { + { + PolylineSceneLayer::Chain chain; + chain.push_back(side1End_); + chain.push_back(center_); - const Color color(TOOL_ANGLE_LINES_COLOR_RED, TOOL_ANGLE_LINES_COLOR_GREEN, TOOL_ANGLE_LINES_COLOR_BLUE); - const Color highlightColor(TOOL_ANGLE_LINES_HL_COLOR_RED, TOOL_ANGLE_LINES_HL_COLOR_GREEN, TOOL_ANGLE_LINES_HL_COLOR_BLUE); + if ((angleHighlightArea_ == AngleHighlightArea_Side1) || + (angleHighlightArea_ == AngleHighlightArea_Side2)) + { + polylineLayer->AddChain(chain, false, highlightColor); + } + else + { + polylineLayer->AddChain(chain, false, color); + } + } + { + PolylineSceneLayer::Chain chain; + chain.push_back(side2End_); + chain.push_back(center_); + if ((angleHighlightArea_ == AngleHighlightArea_Side1) || + (angleHighlightArea_ == AngleHighlightArea_Side2)) + { + polylineLayer->AddChain(chain, false, highlightColor); + } + else + { + polylineLayer->AddChain(chain, false, color); + } + } + } - // sides - { + // Create the handles + { + { + PolylineSceneLayer::Chain chain; + //TODO: take DPI into account + AddSquare(chain, controller.GetScene(), side1End_, + controller.GetHandleSideLengthS()); + + if (angleHighlightArea_ == AngleHighlightArea_Side1End) + polylineLayer->AddChain(chain, true, highlightColor); + else + polylineLayer->AddChain(chain, true, color); + + } + { + PolylineSceneLayer::Chain chain; + //TODO: take DPI into account + AddSquare(chain, controller.GetScene(), side2End_, + controller.GetHandleSideLengthS()); + + if (angleHighlightArea_ == AngleHighlightArea_Side2End) + polylineLayer->AddChain(chain, true, highlightColor); + else + polylineLayer->AddChain(chain, true, color); + } + } + + // Create the arc { PolylineSceneLayer::Chain chain; - chain.push_back(side1End_); - chain.push_back(center_); - if ((angleHighlightArea_ == AngleHighlightArea_Side1) || (angleHighlightArea_ == AngleHighlightArea_Side2)) - polylineLayer->AddChain(chain, false, highlightColor); - else - polylineLayer->AddChain(chain, false, color); - } - { - PolylineSceneLayer::Chain chain; - chain.push_back(side2End_); - chain.push_back(center_); - if ((angleHighlightArea_ == AngleHighlightArea_Side1) || (angleHighlightArea_ == AngleHighlightArea_Side2)) + AddShortestArc(chain, side1End_, center_, side2End_, + controller.GetAngleToolArcRadiusS()); + if (angleHighlightArea_ == AngleHighlightArea_Center) polylineLayer->AddChain(chain, false, highlightColor); else polylineLayer->AddChain(chain, false, color); } } - - // Create the handles - { - { - PolylineSceneLayer::Chain chain; - //TODO: take DPI into account - AddSquare(chain, GetController()->GetScene(), side1End_, - GetController()->GetHandleSideLengthS()); - - if (angleHighlightArea_ == AngleHighlightArea_Side1End) - polylineLayer->AddChain(chain, true, highlightColor); - else - polylineLayer->AddChain(chain, true, color); - - } - { - PolylineSceneLayer::Chain chain; - //TODO: take DPI into account - AddSquare(chain, GetController()->GetScene(), side2End_, - GetController()->GetHandleSideLengthS()); - - if (angleHighlightArea_ == AngleHighlightArea_Side2End) - polylineLayer->AddChain(chain, true, highlightColor); - else - polylineLayer->AddChain(chain, true, color); - } - } - - // Create the arc - { - PolylineSceneLayer::Chain chain; - - AddShortestArc(chain, side1End_, center_, side2End_, - controller->GetAngleToolArcRadiusS()); - if (angleHighlightArea_ == AngleHighlightArea_Center) - polylineLayer->AddChain(chain, false, highlightColor); - else - polylineLayer->AddChain(chain, false, color); - } } { // Set the text layer @@ -293,8 +344,8 @@ double delta = NormalizeAngle(p2cAngle - p1cAngle); double theta = p1cAngle + delta / 2; - double ox = controller->GetAngleTopTextLabelDistanceS() * cos(theta); - double oy = controller->GetAngleTopTextLabelDistanceS() * sin(theta); + double ox = controller.GetAngleTopTextLabelDistanceS() * cos(theta); + double oy = controller.GetAngleTopTextLabelDistanceS() * sin(theta); double pointX = center_.GetX() + ox; double pointY = center_.GetY() + oy; @@ -307,10 +358,10 @@ #if ORTHANC_STONE_ENABLE_OUTLINED_TEXT == 1 SetTextLayerOutlineProperties( - GetController()->GetScene(), layerHolder_, buf, ScenePoint2D(pointX, pointY), 0); + scene, layerHolder_, buf, ScenePoint2D(pointX, pointY), 0); #else SetTextLayerProperties( - GetController()->GetScene(), layerHolder_, buf, ScenePoint2D(pointX, pointY) , 0); + scene, layerHolder_, buf, ScenePoint2D(pointX, pointY) , 0); #endif #if 0 @@ -370,6 +421,7 @@ { RemoveFromScene(); } + lock->Invalidate(); } } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/AngleMeasureTool.h --- a/Framework/Scene2DViewport/AngleMeasureTool.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/AngleMeasureTool.h Wed Apr 22 14:05:47 2020 +0200 @@ -37,10 +37,10 @@ namespace OrthancStone { - class AngleMeasureTool : public MeasureTool, public boost::enable_shared_from_this + class AngleMeasureTool : public MeasureTool { public: - AngleMeasureTool(MessageBroker& broker, boost::weak_ptr controllerW); + static boost::shared_ptr Create(boost::shared_ptr viewport); ~AngleMeasureTool(); @@ -48,7 +48,7 @@ void SetCenter(ScenePoint2D start); void SetSide2End(ScenePoint2D start); - virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE; + virtual bool HitTest(ScenePoint2D p) ORTHANC_OVERRIDE; virtual void Highlight(ScenePoint2D p) ORTHANC_OVERRIDE; virtual void ResetHighlightState() ORTHANC_OVERRIDE; virtual boost::shared_ptr CreateEditionTracker(const PointerEvent& e) ORTHANC_OVERRIDE; @@ -70,6 +70,8 @@ AngleHighlightArea AngleHitTest(ScenePoint2D p) const; private: + AngleMeasureTool(boost::shared_ptr viewport); + virtual void RefreshScene() ORTHANC_OVERRIDE; void RemoveFromScene(); void SetAngleHighlightArea(AngleHighlightArea area); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/CreateAngleMeasureCommand.cpp --- a/Framework/Scene2DViewport/CreateAngleMeasureCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/CreateAngleMeasureCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -26,14 +26,16 @@ namespace OrthancStone { CreateAngleMeasureCommand::CreateAngleMeasureCommand( - MessageBroker& broker, - boost::weak_ptr controllerW, + boost::shared_ptr viewport, ScenePoint2D point) - : CreateMeasureCommand(controllerW) - , measureTool_( - boost::make_shared(boost::ref(broker), controllerW)) + : CreateMeasureCommand(viewport) + , measureTool_(AngleMeasureTool::Create(viewport)) { - GetController()->AddMeasureTool(measureTool_); + + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + + controller.AddMeasureTool(measureTool_); measureTool_->SetSide1End(point); measureTool_->SetCenter(point); measureTool_->SetSide2End(point); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/CreateAngleMeasureCommand.h --- a/Framework/Scene2DViewport/CreateAngleMeasureCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/CreateAngleMeasureCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -28,8 +28,7 @@ public: /** Ctor sets end of side 1*/ CreateAngleMeasureCommand( - MessageBroker& broker, - boost::weak_ptr controllerW, + boost::shared_ptr viewport, ScenePoint2D point); /** This method sets center*/ diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp --- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -26,17 +26,18 @@ namespace OrthancStone { CreateAngleMeasureTracker::CreateAngleMeasureTracker( - MessageBroker& broker, - boost::weak_ptr controllerW, - const PointerEvent& e) - : CreateMeasureTracker(controllerW) + boost::shared_ptr viewport, + const PointerEvent& e) + : CreateMeasureTracker(viewport) , state_(CreatingSide1) { - command_.reset( - new CreateAngleMeasureCommand( - broker, - controllerW, - e.GetMainPosition().Apply(GetScene().GetCanvasToSceneTransform()))); + ScenePoint2D point = e.GetMainPosition(); + { + std::unique_ptr lock(viewport_->Lock()); + Scene2D& scene = lock->GetController().GetScene(); + point = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); + } + command_.reset(new CreateAngleMeasureCommand(viewport, point)); } CreateAngleMeasureTracker::~CreateAngleMeasureTracker() @@ -52,24 +53,31 @@ "PointerMove: active_ == false"); } - ScenePoint2D scenePos = event.GetMainPosition().Apply( - GetScene().GetCanvasToSceneTransform()); - - switch (state_) + { - case CreatingSide1: - GetCommand()->SetCenter(scenePos); - break; - case CreatingSide2: - GetCommand()->SetSide2End(scenePos); - break; - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "Wrong state in CreateAngleMeasureTracker::" - "PointerMove: state_ invalid"); + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + + ScenePoint2D scenePos = event.GetMainPosition().Apply( + controller.GetScene().GetCanvasToSceneTransform()); + + switch (state_) + { + case CreatingSide1: + GetCommand()->SetCenter(scenePos); + break; + case CreatingSide2: + GetCommand()->SetSide2End(scenePos); + break; + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Wrong state in CreateAngleMeasureTracker::" + "PointerMove: state_ invalid"); + } + //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << " " << + // "scenePos.GetY() = " << scenePos.GetY(); + lock->Invalidate(); } - //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << " " << - // "scenePos.GetY() = " << scenePos.GetY(); } void CreateAngleMeasureTracker::PointerUp(const PointerEvent& e) diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/CreateAngleMeasureTracker.h --- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.h Wed Apr 22 14:05:47 2020 +0200 @@ -38,8 +38,7 @@ must be supplied, too */ CreateAngleMeasureTracker( - MessageBroker& broker, - boost::weak_ptr controllerW, + boost::shared_ptr viewport, const PointerEvent& e); ~CreateAngleMeasureTracker(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/CreateLineMeasureCommand.cpp --- a/Framework/Scene2DViewport/CreateLineMeasureCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/CreateLineMeasureCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -26,15 +26,17 @@ namespace OrthancStone { CreateLineMeasureCommand::CreateLineMeasureCommand( - MessageBroker& broker, - boost::weak_ptr controllerW, + boost::shared_ptr viewport, ScenePoint2D point) - : CreateMeasureCommand(controllerW) - , measureTool_( - boost::make_shared(boost::ref(broker), controllerW)) + : CreateMeasureCommand(viewport) + , measureTool_(LineMeasureTool::Create(viewport)) { - GetController()->AddMeasureTool(measureTool_); + + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + controller.AddMeasureTool(measureTool_); measureTool_->Set(point, point); + lock->Invalidate(); } void CreateLineMeasureCommand::SetEnd(ScenePoint2D scenePos) diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/CreateLineMeasureCommand.h --- a/Framework/Scene2DViewport/CreateLineMeasureCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/CreateLineMeasureCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -27,8 +27,7 @@ { public: CreateLineMeasureCommand( - MessageBroker& broker, - boost::weak_ptr controllerW, + boost::shared_ptr viewport, ScenePoint2D point); // the starting position is set in the ctor diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/CreateLineMeasureTracker.cpp --- a/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -26,16 +26,17 @@ namespace OrthancStone { CreateLineMeasureTracker::CreateLineMeasureTracker( - MessageBroker& broker, - boost::weak_ptr controllerW, + boost::shared_ptr viewport, const PointerEvent& e) - : CreateMeasureTracker(controllerW) + : CreateMeasureTracker(viewport) { - command_.reset( - new CreateLineMeasureCommand( - broker, - controllerW, - e.GetMainPosition().Apply(GetScene().GetCanvasToSceneTransform()))); + ScenePoint2D point = e.GetMainPosition(); + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + point = e.GetMainPosition().Apply(controller.GetScene().GetCanvasToSceneTransform()); + } + command_.reset(new CreateLineMeasureCommand(viewport, point)); } CreateLineMeasureTracker::~CreateLineMeasureTracker() @@ -52,12 +53,15 @@ "PointerMove: active_ == false"); } + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + ScenePoint2D scenePos = event.GetMainPosition().Apply( - GetScene().GetCanvasToSceneTransform()); - + controller.GetScene().GetCanvasToSceneTransform()); + //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << " " << // "scenePos.GetY() = " << scenePos.GetY(); - + CreateLineMeasureTracker* concreteThis = dynamic_cast(this); assert(concreteThis != NULL); @@ -84,5 +88,4 @@ { return boost::dynamic_pointer_cast(command_); } - } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/CreateLineMeasureTracker.h --- a/Framework/Scene2DViewport/CreateLineMeasureTracker.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.h Wed Apr 22 14:05:47 2020 +0200 @@ -38,8 +38,7 @@ must be supplied, too */ CreateLineMeasureTracker( - MessageBroker& broker, - boost::weak_ptr controllerW, + boost::shared_ptr viewport, const PointerEvent& e); ~CreateLineMeasureTracker(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/EditAngleMeasureCommand.cpp --- a/Framework/Scene2DViewport/EditAngleMeasureCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/EditAngleMeasureCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -23,31 +23,30 @@ namespace OrthancStone { EditAngleMeasureCommand::EditAngleMeasureCommand( - boost::shared_ptr measureTool, - MessageBroker& broker, - boost::weak_ptr controllerW) - : EditMeasureCommand(measureTool, controllerW) + boost::shared_ptr measureTool, + boost::shared_ptr viewport) + : EditMeasureCommand(measureTool, viewport) , measureTool_(measureTool) { } void EditAngleMeasureCommand::SetCenter(ScenePoint2D scenePos) { - measureTool_->SetCenter(scenePos); + dynamic_cast(*measureTool_).SetCenter(scenePos); mementoModified_ = measureTool_->GetMemento(); } void EditAngleMeasureCommand::SetSide1End(ScenePoint2D scenePos) { - measureTool_->SetSide1End(scenePos); + dynamic_cast(*measureTool_).SetSide1End(scenePos); mementoModified_ = measureTool_->GetMemento(); } void EditAngleMeasureCommand::SetSide2End(ScenePoint2D scenePos) { - measureTool_->SetSide2End(scenePos); + dynamic_cast(*measureTool_).SetSide2End(scenePos); mementoModified_ = measureTool_->GetMemento(); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/EditAngleMeasureCommand.h --- a/Framework/Scene2DViewport/EditAngleMeasureCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/EditAngleMeasureCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -28,9 +28,8 @@ public: /** Ctor sets end of side 1*/ EditAngleMeasureCommand( - boost::shared_ptr measureTool, - MessageBroker& broker, - boost::weak_ptr controllerW); + boost::shared_ptr measureTool, + boost::shared_ptr viewport); /** This method sets center*/ void SetCenter(ScenePoint2D scenePos); @@ -46,6 +45,6 @@ { return measureTool_; } - boost::shared_ptr measureTool_; + boost::shared_ptr measureTool_; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/EditAngleMeasureTracker.cpp --- a/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -26,18 +26,19 @@ namespace OrthancStone { EditAngleMeasureTracker::EditAngleMeasureTracker( - boost::shared_ptr measureTool, - MessageBroker& broker, - boost::weak_ptr controllerW, + boost::shared_ptr measureTool, + boost::shared_ptr viewport, const PointerEvent& e) - : EditMeasureTracker(controllerW, e) + : EditMeasureTracker(viewport, e) { - ScenePoint2D scenePos = e.GetMainPosition().Apply( - GetScene().GetCanvasToSceneTransform()); - - modifiedZone_ = measureTool->AngleHitTest(scenePos); - - command_.reset(new EditAngleMeasureCommand(measureTool, broker, controllerW)); + ScenePoint2D scenePos = e.GetMainPosition(); + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + scenePos = e.GetMainPosition().Apply(controller.GetScene().GetCanvasToSceneTransform()); + } + modifiedZone_ = dynamic_cast(*measureTool).AngleHitTest(scenePos); + command_.reset(new EditAngleMeasureCommand(measureTool, viewport)); } EditAngleMeasureTracker::~EditAngleMeasureTracker() @@ -47,8 +48,12 @@ void EditAngleMeasureTracker::PointerMove(const PointerEvent& e) { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + ScenePoint2D scenePos = e.GetMainPosition().Apply( - GetScene().GetCanvasToSceneTransform()); + scene.GetCanvasToSceneTransform()); ScenePoint2D delta = scenePos - GetOriginalClickPosition(); @@ -59,38 +64,38 @@ switch (modifiedZone_) { - case AngleMeasureTool::AngleHighlightArea_Center: - { - ScenePoint2D newCenter = memento->center_ + delta; - GetCommand()->SetCenter(newCenter); - } - break; - case AngleMeasureTool::AngleHighlightArea_Side1: - case AngleMeasureTool::AngleHighlightArea_Side2: - { - ScenePoint2D newCenter = memento->center_ + delta; - ScenePoint2D newSide1End = memento->side1End_ + delta; - ScenePoint2D newSide2End = memento->side2End_ + delta; - GetCommand()->SetCenter(newCenter); - GetCommand()->SetSide1End(newSide1End); - GetCommand()->SetSide2End(newSide2End); - } - break; - case AngleMeasureTool::AngleHighlightArea_Side1End: - { - ScenePoint2D newSide1End = memento->side1End_ + delta; - GetCommand()->SetSide1End(newSide1End); - } - break; - case AngleMeasureTool::AngleHighlightArea_Side2End: - { - ScenePoint2D newSide2End = memento->side2End_ + delta; - GetCommand()->SetSide2End(newSide2End); - } - break; - default: - LOG(WARNING) << "Warning: please retry the measuring tool editing operation!"; + case AngleMeasureTool::AngleHighlightArea_Center: + { + ScenePoint2D newCenter = memento->center_ + delta; + GetCommand()->SetCenter(newCenter); + } break; + case AngleMeasureTool::AngleHighlightArea_Side1: + case AngleMeasureTool::AngleHighlightArea_Side2: + { + ScenePoint2D newCenter = memento->center_ + delta; + ScenePoint2D newSide1End = memento->side1End_ + delta; + ScenePoint2D newSide2End = memento->side2End_ + delta; + GetCommand()->SetCenter(newCenter); + GetCommand()->SetSide1End(newSide1End); + GetCommand()->SetSide2End(newSide2End); + } + break; + case AngleMeasureTool::AngleHighlightArea_Side1End: + { + ScenePoint2D newSide1End = memento->side1End_ + delta; + GetCommand()->SetSide1End(newSide1End); + } + break; + case AngleMeasureTool::AngleHighlightArea_Side2End: + { + ScenePoint2D newSide2End = memento->side2End_ + delta; + GetCommand()->SetSide2End(newSide2End); + } + break; + default: + LOG(WARNING) << "Warning: please retry the measuring tool editing operation!"; + break; } } @@ -111,5 +116,4 @@ ORTHANC_ASSERT(ret.get() != NULL, "Internal error in EditAngleMeasureTracker::GetCommand()"); return ret; } - } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/EditAngleMeasureTracker.h --- a/Framework/Scene2DViewport/EditAngleMeasureTracker.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.h Wed Apr 22 14:05:47 2020 +0200 @@ -37,9 +37,8 @@ must be supplied, too */ EditAngleMeasureTracker( - boost::shared_ptr measureTool, - MessageBroker& broker, - boost::weak_ptr controllerW, + boost::shared_ptr measureTool, + boost::shared_ptr viewport, const PointerEvent& e); ~EditAngleMeasureTracker(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/EditLineMeasureCommand.cpp --- a/Framework/Scene2DViewport/EditLineMeasureCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/EditLineMeasureCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -23,10 +23,9 @@ namespace OrthancStone { EditLineMeasureCommand::EditLineMeasureCommand( - boost::shared_ptr measureTool, - MessageBroker& broker, - boost::weak_ptr controllerW) - : EditMeasureCommand(measureTool, controllerW) + boost::shared_ptr measureTool, + boost::shared_ptr viewport) + : EditMeasureCommand(measureTool, viewport) , measureTool_(measureTool) { } @@ -34,14 +33,14 @@ void EditLineMeasureCommand::SetStart(ScenePoint2D scenePos) { - measureTool_->SetStart(scenePos); + dynamic_cast(*measureTool_).SetStart(scenePos); mementoModified_ = measureTool_->GetMemento(); } void EditLineMeasureCommand::SetEnd(ScenePoint2D scenePos) { - measureTool_->SetEnd(scenePos); + dynamic_cast(*measureTool_).SetEnd(scenePos); mementoModified_ = measureTool_->GetMemento(); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/EditLineMeasureCommand.h --- a/Framework/Scene2DViewport/EditLineMeasureCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/EditLineMeasureCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -27,9 +27,8 @@ { public: EditLineMeasureCommand( - boost::shared_ptr measureTool, - MessageBroker& broker, - boost::weak_ptr controllerW); + boost::shared_ptr measureTool, + boost::shared_ptr viewport); void SetStart(ScenePoint2D scenePos); void SetEnd(ScenePoint2D scenePos); @@ -39,7 +38,6 @@ { return measureTool_; } - boost::shared_ptr measureTool_; + boost::shared_ptr measureTool_; }; } - diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/EditLineMeasureTracker.cpp --- a/Framework/Scene2DViewport/EditLineMeasureTracker.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/EditLineMeasureTracker.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -27,22 +27,19 @@ namespace OrthancStone { EditLineMeasureTracker::EditLineMeasureTracker( - boost::shared_ptr measureTool, - MessageBroker& broker, - boost::weak_ptr controllerW, - const PointerEvent& e) - : EditMeasureTracker(controllerW, e) + boost::shared_ptr measureTool, + boost::shared_ptr viewport, + const PointerEvent& e) + : EditMeasureTracker(viewport, e) { - ScenePoint2D scenePos = e.GetMainPosition().Apply( - GetScene().GetCanvasToSceneTransform()); - - modifiedZone_ = measureTool->LineHitTest(scenePos); - - command_.reset( - new EditLineMeasureCommand( - measureTool, - broker, - controllerW)); + ScenePoint2D scenePos = e.GetMainPosition(); + { + std::unique_ptr lock(viewport_->Lock()); + Scene2D& scene = lock->GetController().GetScene(); + scenePos = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); + } + modifiedZone_ = dynamic_cast(*measureTool).LineHitTest(scenePos); + command_.reset(new EditLineMeasureCommand(measureTool, viewport)); } EditLineMeasureTracker::~EditLineMeasureTracker() @@ -52,8 +49,12 @@ void EditLineMeasureTracker::PointerMove(const PointerEvent& e) { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + ScenePoint2D scenePos = e.GetMainPosition().Apply( - GetScene().GetCanvasToSceneTransform()); + scene.GetCanvasToSceneTransform()); ScenePoint2D delta = scenePos - GetOriginalClickPosition(); @@ -86,7 +87,7 @@ break; default: LOG(WARNING) << "Warning: please retry the measuring tool editing operation!"; - break; + break; } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/EditLineMeasureTracker.h --- a/Framework/Scene2DViewport/EditLineMeasureTracker.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/EditLineMeasureTracker.h Wed Apr 22 14:05:47 2020 +0200 @@ -37,9 +37,8 @@ must be supplied, too */ EditLineMeasureTracker( - boost::shared_ptr measureTool, - MessageBroker& broker, - boost::weak_ptr controllerW, + boost::shared_ptr measureTool, + boost::shared_ptr viewport, const PointerEvent& e); ~EditLineMeasureTracker(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/LayerHolder.cpp --- a/Framework/Scene2DViewport/LayerHolder.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/LayerHolder.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -22,20 +22,20 @@ #include "../Scene2D/TextSceneLayer.h" #include "../Scene2D/PolylineSceneLayer.h" #include "../Scene2D/Scene2D.h" -#include "../Scene2DViewport/ViewportController.h" +#include "../Viewport/IViewport.h" #include "../StoneException.h" namespace OrthancStone { LayerHolder::LayerHolder( - boost::weak_ptr controllerW, - int polylineLayerCount, - int textLayerCount, - int infoTextCount) + boost::shared_ptr viewport, + int polylineLayerCount, + int textLayerCount, + int infoTextCount) : textLayerCount_(textLayerCount) , polylineLayerCount_(polylineLayerCount) , infoTextCount_(infoTextCount) - , controllerW_(controllerW) + , viewport_(viewport) , baseLayerIndex_(-1) { @@ -43,24 +43,26 @@ void LayerHolder::CreateLayers() { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + assert(baseLayerIndex_ == -1); - baseLayerIndex_ = GetScene().GetMaxDepth() + 100; + baseLayerIndex_ = scene.GetMaxDepth() + 100; for (int i = 0; i < polylineLayerCount_; ++i) { std::unique_ptr layer(new PolylineSceneLayer()); - GetScene().SetLayer(baseLayerIndex_ + i, layer.release()); + scene.SetLayer(baseLayerIndex_ + i, layer.release()); } for (int i = 0; i < textLayerCount_; ++i) { std::unique_ptr layer(new TextSceneLayer()); - GetScene().SetLayer( - baseLayerIndex_ + polylineLayerCount_ + i, - layer.release()); + scene.SetLayer(baseLayerIndex_ + polylineLayerCount_ + i, layer.release()); } - + lock->Invalidate(); } void LayerHolder::CreateLayersIfNeeded() @@ -74,13 +76,6 @@ return (baseLayerIndex_ != -1); } - Scene2D& LayerHolder::GetScene() - { - boost::shared_ptr controller = controllerW_.lock(); - ORTHANC_ASSERT(controller.get() != 0, "Zombie attack!"); - return controller->GetScene(); - } - void LayerHolder::DeleteLayersIfNeeded() { if (baseLayerIndex_ != -1) @@ -89,40 +84,48 @@ void LayerHolder::DeleteLayers() { + std::unique_ptr lock(viewport_->Lock()); + Scene2D& scene = lock->GetController().GetScene(); + for (int i = 0; i < textLayerCount_ + polylineLayerCount_; ++i) { - ORTHANC_ASSERT(GetScene().HasLayer(baseLayerIndex_ + i), "No layer"); - GetScene().DeleteLayer(baseLayerIndex_ + i); + ORTHANC_ASSERT(scene.HasLayer(baseLayerIndex_ + i), "No layer"); + scene.DeleteLayer(baseLayerIndex_ + i); } baseLayerIndex_ = -1; + lock->Invalidate(); } - + PolylineSceneLayer* LayerHolder::GetPolylineLayer(int index /*= 0*/) { + std::unique_ptr lock(viewport_->Lock()); + Scene2D& scene = lock->GetController().GetScene(); + using namespace Orthanc; ORTHANC_ASSERT(baseLayerIndex_ != -1); - ORTHANC_ASSERT(GetScene().HasLayer(GetPolylineLayerIndex(index))); - ISceneLayer* layer = - &(GetScene().GetLayer(GetPolylineLayerIndex(index))); - + ORTHANC_ASSERT(scene.HasLayer(GetPolylineLayerIndex(index))); + ISceneLayer* layer = &(scene.GetLayer(GetPolylineLayerIndex(index))); + PolylineSceneLayer* concreteLayer = dynamic_cast(layer); - + ORTHANC_ASSERT(concreteLayer != NULL); return concreteLayer; } TextSceneLayer* LayerHolder::GetTextLayer(int index /*= 0*/) { + std::unique_ptr lock(viewport_->Lock()); + Scene2D& scene = lock->GetController().GetScene(); + using namespace Orthanc; ORTHANC_ASSERT(baseLayerIndex_ != -1); - ORTHANC_ASSERT(GetScene().HasLayer(GetTextLayerIndex(index))); - ISceneLayer* layer = - &(GetScene().GetLayer(GetTextLayerIndex(index))); - + ORTHANC_ASSERT(scene.HasLayer(GetTextLayerIndex(index))); + ISceneLayer* layer = &(scene.GetLayer(GetTextLayerIndex(index))); + TextSceneLayer* concreteLayer = dynamic_cast(layer); - + ORTHANC_ASSERT(concreteLayer != NULL); return concreteLayer; } @@ -133,8 +136,7 @@ ORTHANC_ASSERT(index < polylineLayerCount_); return baseLayerIndex_ + index; } - - + int LayerHolder::GetTextLayerIndex(int index /*= 0*/) { using namespace Orthanc; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/LayerHolder.h --- a/Framework/Scene2DViewport/LayerHolder.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/LayerHolder.h Wed Apr 22 14:05:47 2020 +0200 @@ -43,7 +43,7 @@ performed at this time */ LayerHolder( - boost::weak_ptr controllerW, + boost::shared_ptr viewport, int polylineLayerCount, int textLayerCount, int infoTextCount = 0); /** @@ -97,12 +97,11 @@ int GetPolylineLayerIndex(int index = 0); int GetTextLayerIndex(int index = 0); int GetInfoTextLayerIndex(int index = 0); - Scene2D& GetScene(); int textLayerCount_; int polylineLayerCount_; int infoTextCount_; - boost::weak_ptr controllerW_; + boost::shared_ptr viewport_; int baseLayerIndex_; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/LineMeasureTool.cpp --- a/Framework/Scene2DViewport/LineMeasureTool.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/LineMeasureTool.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -32,18 +32,26 @@ { LineMeasureTool::LineMeasureTool( - MessageBroker& broker, boost::weak_ptr controllerW) - : MeasureTool(broker, controllerW) + boost::shared_ptr viewport) + : MeasureTool(viewport) #if ORTHANC_STONE_ENABLE_OUTLINED_TEXT == 1 - , layerHolder_(boost::make_shared(controllerW, 1, 5)) + , layerHolder_(boost::shared_ptr(new LayerHolder(viewport,1,5))) #else - , layerHolder_(boost::make_shared(controllerW, 1, 1)) + , layerHolder_(boost::shared_ptr(new LayerHolder(viewport,1,1))) #endif , lineHighlightArea_(LineHighlightArea_None) { } + boost::shared_ptr LineMeasureTool::Create(boost::shared_ptr viewport) + { + boost::shared_ptr obj(new LineMeasureTool(viewport)); + obj->MeasureTool::PostConstructor(); + obj->RefreshScene(); + return obj; + } + LineMeasureTool::~LineMeasureTool() { // this measuring tool is a RABI for the corresponding visual layers @@ -106,53 +114,59 @@ SetLineHighlightArea(lineHighlightArea); } - LineMeasureTool::LineHighlightArea LineMeasureTool::LineHitTest(ScenePoint2D p) const + LineMeasureTool::LineHighlightArea LineMeasureTool::LineHitTest(ScenePoint2D p) { - const double pixelToScene = - GetController()->GetScene().GetCanvasToSceneTransform().ComputeZoom(); - const double SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD = pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD * pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD; + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); - const double sqDistanceFromStart = ScenePoint2D::SquaredDistancePtPt(p, start_); + const double pixelToScene = scene.GetCanvasToSceneTransform().ComputeZoom(); + const double SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD = + pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD * + pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD; + + const double sqDistanceFromStart = + ScenePoint2D::SquaredDistancePtPt(p, start_); + if (sqDistanceFromStart <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD) return LineHighlightArea_Start; const double sqDistanceFromEnd = ScenePoint2D::SquaredDistancePtPt(p, end_); + if (sqDistanceFromEnd <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD) return LineHighlightArea_End; - const double sqDistanceFromPtSegment = ScenePoint2D::SquaredDistancePtSegment(start_, end_, p); + const double sqDistanceFromPtSegment = + ScenePoint2D::SquaredDistancePtSegment(start_, end_, p); + if (sqDistanceFromPtSegment <= SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD) return LineHighlightArea_Segment; return LineHighlightArea_None; } - bool LineMeasureTool::HitTest(ScenePoint2D p) const + bool LineMeasureTool::HitTest(ScenePoint2D p) { return LineHitTest(p) != LineHighlightArea_None; } boost::shared_ptr LineMeasureTool::CreateEditionTracker(const PointerEvent& e) { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + ScenePoint2D scenePos = e.GetMainPosition().Apply( - GetController()->GetScene().GetCanvasToSceneTransform()); + scene.GetCanvasToSceneTransform()); if (!HitTest(scenePos)) return boost::shared_ptr(); - /** - new EditLineMeasureTracker( - boost::shared_ptr measureTool; - MessageBroker & broker, - boost::weak_ptr controllerW, - const PointerEvent & e); - */ boost::shared_ptr editLineMeasureTracker( - new EditLineMeasureTracker(shared_from_this(), GetBroker(), GetController(), e)); + new EditLineMeasureTracker(shared_from_this(), viewport_, e)); return editLineMeasureTracker; } - boost::shared_ptr LineMeasureTool::GetMemento() const { boost::shared_ptr memento(new LineMeasureToolMemento()); @@ -161,10 +175,14 @@ return memento; } - void LineMeasureTool::SetMemento(boost::shared_ptr mementoBase) + void LineMeasureTool::SetMemento( + boost::shared_ptr mementoBase) { - boost::shared_ptr memento = boost::dynamic_pointer_cast(mementoBase); + boost::shared_ptr memento = + boost::dynamic_pointer_cast(mementoBase); + ORTHANC_ASSERT(memento.get() != NULL, "Internal error: wrong (or bad) memento"); + start_ = memento->start_; end_ = memento->end_; RefreshScene(); @@ -176,61 +194,67 @@ { if (IsEnabled()) { + + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + layerHolder_->CreateLayersIfNeeded(); - { // Fill the polyline layer with the measurement line PolylineSceneLayer* polylineLayer = layerHolder_->GetPolylineLayer(0); - polylineLayer->ClearAllChains(); - - const Color color(TOOL_LINES_COLOR_RED, - TOOL_LINES_COLOR_GREEN, - TOOL_LINES_COLOR_BLUE); - - const Color highlightColor(TOOL_LINES_HL_COLOR_RED, - TOOL_LINES_HL_COLOR_GREEN, - TOOL_LINES_HL_COLOR_BLUE); - + if (polylineLayer) { - PolylineSceneLayer::Chain chain; - chain.push_back(start_); - chain.push_back(end_); - if(lineHighlightArea_ == LineHighlightArea_Segment) - polylineLayer->AddChain(chain, false, highlightColor); - else - polylineLayer->AddChain(chain, false, color); - } + polylineLayer->ClearAllChains(); - // handles - { - { - PolylineSceneLayer::Chain chain; - - //TODO: take DPI into account - AddSquare(chain, GetController()->GetScene(), start_, - GetController()->GetHandleSideLengthS()); - - if (lineHighlightArea_ == LineHighlightArea_Start) - polylineLayer->AddChain(chain, true, highlightColor); - else - polylineLayer->AddChain(chain, true, color); - } + const Color color(TOOL_LINES_COLOR_RED, + TOOL_LINES_COLOR_GREEN, + TOOL_LINES_COLOR_BLUE); + + const Color highlightColor(TOOL_LINES_HL_COLOR_RED, + TOOL_LINES_HL_COLOR_GREEN, + TOOL_LINES_HL_COLOR_BLUE); { PolylineSceneLayer::Chain chain; + chain.push_back(start_); + chain.push_back(end_); + if(lineHighlightArea_ == LineHighlightArea_Segment) + polylineLayer->AddChain(chain, false, highlightColor); + else + polylineLayer->AddChain(chain, false, color); + } + + // handles + { + { + PolylineSceneLayer::Chain chain; - //TODO: take DPI into account - AddSquare(chain, GetController()->GetScene(), end_, - GetController()->GetHandleSideLengthS()); + //TODO: take DPI into account + AddSquare(chain, controller.GetScene(), start_, + controller.GetHandleSideLengthS()); - if (lineHighlightArea_ == LineHighlightArea_End) - polylineLayer->AddChain(chain, true, highlightColor); - else - polylineLayer->AddChain(chain, true, color); + if (lineHighlightArea_ == LineHighlightArea_Start) + polylineLayer->AddChain(chain, true, highlightColor); + else + polylineLayer->AddChain(chain, true, color); + } + + { + PolylineSceneLayer::Chain chain; + + //TODO: take DPI into account + AddSquare(chain, controller.GetScene(), end_, + controller.GetHandleSideLengthS()); + + if (lineHighlightArea_ == LineHighlightArea_End) + polylineLayer->AddChain(chain, true, highlightColor); + else + polylineLayer->AddChain(chain, true, color); + } } } - } { // Set the text layer propreties @@ -246,14 +270,19 @@ double midX = 0.5 * (end_.GetX() + start_.GetX()); double midY = 0.5 * (end_.GetY() + start_.GetY()); + { + #if ORTHANC_STONE_ENABLE_OUTLINED_TEXT == 1 - SetTextLayerOutlineProperties( - GetController()->GetScene(), layerHolder_, buf, ScenePoint2D(midX, midY), 0); + SetTextLayerOutlineProperties( + scene, layerHolder_, buf, ScenePoint2D(midX, midY), 0); #else - SetTextLayerProperties( - GetController()->GetScene(), layerHolder_, buf, ScenePoint2D(midX, midY), 0); + SetTextLayerProperties( + scene, layerHolder_, buf, ScenePoint2D(midX, midY), 0); #endif + lock->Invalidate(); + } } + lock->Invalidate(); } else { diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/LineMeasureTool.h --- a/Framework/Scene2DViewport/LineMeasureTool.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/LineMeasureTool.h Wed Apr 22 14:05:47 2020 +0200 @@ -35,10 +35,10 @@ namespace OrthancStone { - class LineMeasureTool : public MeasureTool, public boost::enable_shared_from_this + class LineMeasureTool : public MeasureTool { public: - LineMeasureTool(MessageBroker& broker, boost::weak_ptr controllerW); + static boost::shared_ptr Create(boost::shared_ptr viewport); ~LineMeasureTool(); @@ -47,7 +47,7 @@ void Set(ScenePoint2D start, ScenePoint2D end); - virtual bool HitTest(ScenePoint2D p) const ORTHANC_OVERRIDE; + virtual bool HitTest(ScenePoint2D p) ORTHANC_OVERRIDE; virtual void Highlight(ScenePoint2D p) ORTHANC_OVERRIDE; virtual void ResetHighlightState() ORTHANC_OVERRIDE; virtual boost::shared_ptr CreateEditionTracker(const PointerEvent& e) ORTHANC_OVERRIDE; @@ -64,9 +64,11 @@ }; - LineHighlightArea LineHitTest(ScenePoint2D p) const; + LineHighlightArea LineHitTest(ScenePoint2D p); private: + LineMeasureTool(boost::shared_ptr viewport); + virtual void RefreshScene() ORTHANC_OVERRIDE; void RemoveFromScene(); void SetLineHighlightArea(LineHighlightArea area); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/MeasureCommands.cpp --- a/Framework/Scene2DViewport/MeasureCommands.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/MeasureCommands.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -20,6 +20,8 @@ #include "MeasureCommands.h" +#include + #include #include @@ -27,19 +29,21 @@ { void CreateMeasureCommand::Undo() { + std::unique_ptr lock(viewport_->Lock()); // simply disable the measure tool upon undo GetMeasureTool()->Disable(); - GetController()->RemoveMeasureTool(GetMeasureTool()); + lock->GetController().RemoveMeasureTool(GetMeasureTool()); } void CreateMeasureCommand::Redo() { + std::unique_ptr lock(viewport_->Lock()); GetMeasureTool()->Enable(); - GetController()->AddMeasureTool(GetMeasureTool()); + lock->GetController().AddMeasureTool(GetMeasureTool()); } - CreateMeasureCommand::CreateMeasureCommand(boost::weak_ptr controllerW) - : MeasureCommand(controllerW) + CreateMeasureCommand::CreateMeasureCommand(boost::shared_ptr viewport) + : MeasureCommand(viewport) { } @@ -52,15 +56,17 @@ void DeleteMeasureCommand::Redo() { + std::unique_ptr lock(viewport_->Lock()); // simply disable the measure tool upon undo GetMeasureTool()->Disable(); - GetController()->RemoveMeasureTool(GetMeasureTool()); + lock->GetController().RemoveMeasureTool(GetMeasureTool()); } void DeleteMeasureCommand::Undo() { + std::unique_ptr lock(viewport_->Lock()); GetMeasureTool()->Enable(); - GetController()->AddMeasureTool(GetMeasureTool()); + lock->GetController().AddMeasureTool(GetMeasureTool()); } DeleteMeasureCommand::~DeleteMeasureCommand() @@ -69,18 +75,19 @@ // we thus leave it as is } - DeleteMeasureCommand::DeleteMeasureCommand(boost::shared_ptr measureTool, boost::weak_ptr controllerW) - : MeasureCommand(controllerW) + DeleteMeasureCommand::DeleteMeasureCommand(boost::shared_ptr measureTool, boost::shared_ptr viewport) + : MeasureCommand(viewport) , mementoOriginal_(measureTool->GetMemento()) , measureTool_(measureTool) , mementoModified_(measureTool->GetMemento()) { + std::unique_ptr lock(viewport_->Lock()); GetMeasureTool()->Disable(); - GetController()->RemoveMeasureTool(GetMeasureTool()); + lock->GetController().RemoveMeasureTool(GetMeasureTool()); } - EditMeasureCommand::EditMeasureCommand(boost::shared_ptr measureTool, boost::weak_ptr controllerW) - : MeasureCommand(controllerW) + EditMeasureCommand::EditMeasureCommand(boost::shared_ptr measureTool, boost::shared_ptr viewport) + : MeasureCommand(viewport) , mementoOriginal_(measureTool->GetMemento()) , mementoModified_(measureTool->GetMemento()) { @@ -102,11 +109,4 @@ { GetMeasureTool()->SetMemento(mementoModified_); } - - boost::shared_ptr MeasureCommand::GetController() - { - boost::shared_ptr controller = controllerW_.lock(); - assert(controller); // accessing dead object? - return controller; - } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/MeasureCommands.h --- a/Framework/Scene2DViewport/MeasureCommands.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/MeasureCommands.h Wed Apr 22 14:05:47 2020 +0200 @@ -19,7 +19,7 @@ **/ #pragma once -#include "../Scene2D/Scene2D.h" +#include "../Viewport/IViewport.h" // to be moved into Stone #include "PredeclaredTypes.h" @@ -35,25 +35,21 @@ class MeasureCommand : public boost::noncopyable { public: - MeasureCommand(boost::weak_ptr controllerW) - : controllerW_(controllerW) - { - - } + MeasureCommand(boost::shared_ptr viewport) : viewport_(viewport) + {} virtual void Undo() = 0; virtual void Redo() = 0; virtual ~MeasureCommand() {}; protected: - boost::shared_ptr GetController(); - boost::weak_ptr controllerW_; + boost::shared_ptr viewport_; }; class CreateMeasureCommand : public MeasureCommand { public: - CreateMeasureCommand(boost::weak_ptr controllerW); + CreateMeasureCommand(boost::shared_ptr viewport); virtual ~CreateMeasureCommand(); virtual void Undo() ORTHANC_OVERRIDE; virtual void Redo() ORTHANC_OVERRIDE; @@ -65,7 +61,7 @@ class EditMeasureCommand : public MeasureCommand { public: - EditMeasureCommand(boost::shared_ptr measureTool, boost::weak_ptr controllerW); + EditMeasureCommand(boost::shared_ptr measureTool, boost::shared_ptr viewport); virtual ~EditMeasureCommand(); virtual void Undo() ORTHANC_OVERRIDE; virtual void Redo() ORTHANC_OVERRIDE; @@ -86,7 +82,7 @@ class DeleteMeasureCommand : public MeasureCommand { public: - DeleteMeasureCommand(boost::shared_ptr measureTool, boost::weak_ptr controllerW); + DeleteMeasureCommand(boost::shared_ptr measureTool, boost::shared_ptr viewport); virtual ~DeleteMeasureCommand(); virtual void Undo() ORTHANC_OVERRIDE; virtual void Redo() ORTHANC_OVERRIDE; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/MeasureTool.cpp --- a/Framework/Scene2DViewport/MeasureTool.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/MeasureTool.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -26,16 +26,10 @@ #include +#include "../Viewport/IViewport.h" + namespace OrthancStone { - MeasureTool::~MeasureTool() - { - // if the controller is dead, let's not bother. - boost::shared_ptr controller = controllerW_.lock(); - if (controller) - controller->Unregister(this); - } - void MeasureTool::Enable() { enabled_ = true; @@ -53,48 +47,30 @@ return enabled_; } - - boost::shared_ptr MeasureTool::GetController() const + MeasureTool::MeasureTool( + boost::shared_ptr viewport) + : viewport_(viewport) + , enabled_(true) { - boost::shared_ptr controller = controllerW_.lock(); - if (!controller) - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "Using dead ViewportController object!"); - return controller; + } - boost::shared_ptr MeasureTool::GetController() + void MeasureTool::PostConstructor() { -#if 1 - return boost::const_pointer_cast - (const_cast(this)->GetController()); - //return boost::const_> - // (const_cast(this)->GetController()); -#else - boost::shared_ptr controller = controllerW_.lock(); - if (!controller) - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "Using dead ViewportController object!"); - return controller; -#endif + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + + Register( + controller, + &MeasureTool::OnSceneTransformChanged); } - MeasureTool::MeasureTool(MessageBroker& broker, - boost::weak_ptr controllerW) - : IObserver(broker) - , controllerW_(controllerW) - , enabled_(true) - { - GetController()->RegisterObserverCallback( - new Callable - (*this, &MeasureTool::OnSceneTransformChanged)); - } - - bool MeasureTool::IsSceneAlive() const { - boost::shared_ptr controller = controllerW_.lock(); - return (controller.get() != NULL); + // since the lifetimes of the viewport, viewportcontroller (and the + // measuring tools inside it) are linked, the scene is always alive as + // long as "this" is alive + return true; } void MeasureTool::OnSceneTransformChanged( diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/MeasureTool.h --- a/Framework/Scene2DViewport/MeasureTool.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/MeasureTool.h Wed Apr 22 14:05:47 2020 +0200 @@ -20,6 +20,7 @@ #pragma once +#include "../Messages/ObserverBase.h" #include "../Scene2D/PolylineSceneLayer.h" #include "../Scene2D/Scene2D.h" #include "../Scene2D/ScenePoint2D.h" @@ -27,7 +28,6 @@ #include "../Scene2DViewport/PredeclaredTypes.h" #include "../Scene2DViewport/ViewportController.h" -#include #include #include @@ -38,10 +38,12 @@ class IFlexiblePointerTracker; class MeasureToolMemento; - class MeasureTool : public IObserver + class MeasureTool : public ObserverBase { public: - virtual ~MeasureTool(); + virtual ~MeasureTool() + { + } /** Enabled tools are rendered in the scene. @@ -73,7 +75,7 @@ true, then a click at that position will return a tracker to edit the measuring tool */ - virtual bool HitTest(ScenePoint2D p) const = 0; + virtual bool HitTest(ScenePoint2D p) = 0; /** This method must return a memento the captures the tool state (not including @@ -111,7 +113,9 @@ virtual std::string GetDescription() = 0; protected: - MeasureTool(MessageBroker& broker, boost::weak_ptr controllerW); + MeasureTool(boost::shared_ptr viewport); + + void PostConstructor(); /** The measuring tool may exist in a standalone fashion, without any available @@ -127,17 +131,20 @@ */ virtual void RefreshScene() = 0; - boost::shared_ptr GetController() const; - boost::shared_ptr GetController(); - /** enabled_ is not accessible by subclasses because there is a state machine that we do not wanna mess with */ bool IsEnabled() const; + /** + Protected to allow sub-classes to use this weak pointer in factory methods + (pass them to created objects) + */ + boost::shared_ptr viewport_; + + private: - boost::weak_ptr controllerW_; bool enabled_; }; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/MeasureToolsToolbox.cpp --- a/Framework/Scene2DViewport/MeasureToolsToolbox.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/MeasureToolsToolbox.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -311,28 +311,30 @@ for (int i = startingLayerIndex; i < startingLayerIndex + 5; ++i) { TextSceneLayer* textLayer = layerHolder->GetTextLayer(i); - ORTHANC_ASSERT(textLayer != NULL); - textLayer->SetText(text); - - if (i == startingLayerIndex + 4) - { - textLayer->SetColor(TEXT_COLOR_RED, - TEXT_COLOR_GREEN, - TEXT_COLOR_BLUE); - } - else + if (textLayer != NULL) { - textLayer->SetColor(TEXT_OUTLINE_COLOR_RED, - TEXT_OUTLINE_COLOR_GREEN, - TEXT_OUTLINE_COLOR_BLUE); - } + textLayer->SetText(text); - ScenePoint2D textAnchor; - int offIndex = i - startingLayerIndex; - ORTHANC_ASSERT(offIndex >= 0 && offIndex < 5); - textLayer->SetPosition( - p.GetX() + xoffsets[offIndex] * pixelToScene, - p.GetY() + yoffsets[offIndex] * pixelToScene); + if (i == startingLayerIndex + 4) + { + textLayer->SetColor(TEXT_COLOR_RED, + TEXT_COLOR_GREEN, + TEXT_COLOR_BLUE); + } + else + { + textLayer->SetColor(TEXT_OUTLINE_COLOR_RED, + TEXT_OUTLINE_COLOR_GREEN, + TEXT_OUTLINE_COLOR_BLUE); + } + + ScenePoint2D textAnchor; + int offIndex = i - startingLayerIndex; + ORTHANC_ASSERT(offIndex >= 0 && offIndex < 5); + textLayer->SetPosition( + p.GetX() + xoffsets[offIndex] * pixelToScene, + p.GetY() + yoffsets[offIndex] * pixelToScene); + } } } #else @@ -344,20 +346,21 @@ , int layerIndex) { TextSceneLayer* textLayer = layerHolder->GetTextLayer(layerIndex); - ORTHANC_ASSERT(textLayer != NULL); - textLayer->SetText(text); + if (textLayer != NULL) + { + textLayer->SetText(text); + textLayer->SetColor(TEXT_COLOR_RED, TEXT_COLOR_GREEN, TEXT_COLOR_BLUE); - textLayer->SetColor(TEXT_COLOR_RED, TEXT_COLOR_GREEN, TEXT_COLOR_BLUE); - - ScenePoint2D textAnchor; - textLayer->SetPosition(p.GetX(), p.GetY()); + ScenePoint2D textAnchor; + textLayer->SetPosition(p.GetX(), p.GetY()); + } } #endif - std::ostream& operator<<(std::ostream& os, const ScenePoint2D& p) - { - os << "x = " << p.GetX() << " , y = " << p.GetY(); - return os; - } + std::ostream& operator<<(std::ostream& os, const ScenePoint2D& p) + { + os << "x = " << p.GetX() << " , y = " << p.GetY(); + return os; + } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/MeasureTrackers.cpp --- a/Framework/Scene2DViewport/MeasureTrackers.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/MeasureTrackers.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -24,8 +24,8 @@ namespace OrthancStone { - CreateMeasureTracker::CreateMeasureTracker(boost::weak_ptr controllerW) - : controllerW_(controllerW) + CreateMeasureTracker::CreateMeasureTracker(boost::shared_ptr viewport) + : viewport_(viewport) , alive_(true) , commitResult_(true) { @@ -47,29 +47,28 @@ // if the tracker completes successfully, we add the command // to the undo stack // otherwise, we simply undo it + + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + if (commitResult_) - controllerW_.lock()->PushCommand(command_); + lock->GetController().PushCommand(command_); else command_->Undo(); + + lock->Invalidate(); } - Scene2D& CreateMeasureTracker::GetScene() - { - return controllerW_.lock()->GetScene(); - } - - EditMeasureTracker::EditMeasureTracker(boost::weak_ptr controllerW, const PointerEvent& e) - : controllerW_(controllerW) + EditMeasureTracker::EditMeasureTracker(boost::shared_ptr viewport, const PointerEvent& e) + : viewport_(viewport) , alive_(true) , commitResult_(true) { - boost::shared_ptr controller = controllerW.lock(); - originalClickPosition_ = e.GetMainPosition().Apply(controller->GetScene().GetCanvasToSceneTransform()); - } + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); - Scene2D& EditMeasureTracker::GetScene() - { - return controllerW_.lock()->GetScene(); + originalClickPosition_ = e.GetMainPosition().Apply( + controller.GetScene().GetCanvasToSceneTransform()); } void EditMeasureTracker::Cancel() @@ -88,10 +87,16 @@ // if the tracker completes successfully, we add the command // to the undo stack // otherwise, we simply undo it + + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + if (commitResult_) - controllerW_.lock()->PushCommand(command_); + lock->GetController().PushCommand(command_); else command_->Undo(); + + lock->Invalidate(); } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/MeasureTrackers.h --- a/Framework/Scene2DViewport/MeasureTrackers.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/MeasureTrackers.h Wed Apr 22 14:05:47 2020 +0200 @@ -40,15 +40,14 @@ virtual void Cancel() ORTHANC_OVERRIDE; virtual bool IsAlive() const ORTHANC_OVERRIDE; protected: - CreateMeasureTracker(boost::weak_ptr controllerW); + CreateMeasureTracker(boost::shared_ptr viewport); ~CreateMeasureTracker(); protected: boost::shared_ptr command_; - boost::weak_ptr controllerW_; + boost::shared_ptr viewport_; bool alive_; - Scene2D& GetScene(); private: bool commitResult_; @@ -60,15 +59,14 @@ virtual void Cancel() ORTHANC_OVERRIDE; virtual bool IsAlive() const ORTHANC_OVERRIDE; protected: - EditMeasureTracker(boost::weak_ptr controllerW, const PointerEvent& e); + EditMeasureTracker(boost::shared_ptr viewport, const PointerEvent& e); ~EditMeasureTracker(); protected: boost::shared_ptr command_; - boost::weak_ptr controllerW_; + boost::shared_ptr viewport_; bool alive_; - Scene2D& GetScene(); ScenePoint2D GetOriginalClickPosition() const { diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/OneGesturePointerTracker.cpp --- a/Framework/Scene2DViewport/OneGesturePointerTracker.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/OneGesturePointerTracker.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -28,8 +28,8 @@ namespace OrthancStone { OneGesturePointerTracker::OneGesturePointerTracker( - boost::weak_ptr controllerW) - : controllerW_(controllerW) + boost::shared_ptr viewport) + : viewport_(viewport) , alive_(true) , currentTouchCount_(1) { @@ -41,10 +41,10 @@ // gesture ORTHANC_ASSERT(currentTouchCount_ > 0, "Wrong state in tracker"); currentTouchCount_--; - LOG(INFO) << "currentTouchCount_ becomes: " << currentTouchCount_; + //LOG(TRACE) << "currentTouchCount_ becomes: " << currentTouchCount_; if (currentTouchCount_ == 0) { - LOG(INFO) << "currentTouchCount_ == 0 --> alive_ = false"; + //LOG(TRACE) << "currentTouchCount_ == 0 --> alive_ = false"; alive_ = false; } } @@ -54,16 +54,20 @@ // additional touches are not taken into account but we need to count // the number of active touches currentTouchCount_++; - LOG(INFO) << "currentTouchCount_ becomes: " << currentTouchCount_; + //LOG(TRACE) << "currentTouchCount_ becomes: " << currentTouchCount_; + + /** + * 2019-12-06 (SJO): Patch to have consistent behavior when mouse + * leaves the canvas while the tracker is still active, then + * button is released while out-of-canvas. Such an event is not + * caught (at least in WebAssembly), so we delete the tracker on + * the next click inside the canvas. + **/ + alive_ = false; } bool OneGesturePointerTracker::IsAlive() const { return alive_; } - - boost::shared_ptr OneGesturePointerTracker::GetController() - { - return controllerW_.lock(); - } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/OneGesturePointerTracker.h --- a/Framework/Scene2DViewport/OneGesturePointerTracker.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/OneGesturePointerTracker.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,6 +22,11 @@ #include "IFlexiblePointerTracker.h" +#include "../Viewport/IViewport.h" + +#include +#include + namespace OrthancStone { /** @@ -39,16 +44,15 @@ class OneGesturePointerTracker : public IFlexiblePointerTracker { public: - OneGesturePointerTracker(boost::weak_ptr controllerW); + OneGesturePointerTracker(boost::shared_ptr viewport); virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE; virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE; virtual bool IsAlive() const ORTHANC_OVERRIDE; protected: - boost::shared_ptr GetController(); + boost::shared_ptr viewport_; private: - boost::weak_ptr controllerW_; bool alive_; int currentTouchCount_; }; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/PredeclaredTypes.h --- a/Framework/Scene2DViewport/PredeclaredTypes.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/PredeclaredTypes.h Wed Apr 22 14:05:47 2020 +0200 @@ -38,4 +38,5 @@ class MeasureCommand; class ViewportController; class LayerHolder; + class IViewport; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/ViewportController.cpp --- a/Framework/Scene2DViewport/ViewportController.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/ViewportController.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -24,44 +24,69 @@ #include "MeasureCommands.h" #include "../StoneException.h" +#include "../Scene2D/PanSceneTracker.h" +#include "../Scene2D/RotateSceneTracker.h" +#include "../Scene2D/ZoomSceneTracker.h" #include namespace OrthancStone { - ViewportController::ViewportController(boost::weak_ptr undoStackW, - MessageBroker& broker, - IViewport& viewport) - : IObservable(broker) - , undoStackW_(undoStackW) - , canvasToSceneFactor_(0.0) - , viewport_(viewport) + IFlexiblePointerTracker* DefaultViewportInteractor::CreateTracker( + boost::shared_ptr viewport, + const PointerEvent& event, + unsigned int viewportWidth, + unsigned int viewportHeight) { - } - - ViewportController::~ViewportController() - { + switch (event.GetMouseButton()) + { + case MouseButton_Left: + return new RotateSceneTracker(viewport, event); + case MouseButton_Middle: + return new PanSceneTracker(viewport, event); + + case MouseButton_Right: + { + if (viewportWidth != 0) + { + return new ZoomSceneTracker(viewport, event, viewportWidth); + } + else + { + return NULL; + } + } + + default: + return NULL; + } } - boost::shared_ptr ViewportController::GetUndoStack() + ViewportController::ViewportController(boost::shared_ptr viewport) + : viewport_(viewport) + , scene_(new Scene2D) + , canvasToSceneFactor_(1) { - return undoStackW_.lock(); + // undoStack_ is not default-initialized, which basically means empty. + // The controller must be able to cope with this. } - boost::shared_ptr ViewportController::GetUndoStack() const + ViewportController::~ViewportController() { - return undoStackW_.lock(); } - void ViewportController::PushCommand(boost::shared_ptr command) + void ViewportController::PushCommand( + boost::shared_ptr command) { boost::shared_ptr undoStack = undoStackW_.lock(); - if(undoStack.get() != NULL) + if (undoStack.get() != NULL) + { undoStack->PushCommand(command); + } else { - LOG(ERROR) << "Internal error: no undo stack in the viewport controller!"; + LOG(ERROR) << "Internal error: no undo stack!"; } } @@ -69,10 +94,12 @@ { boost::shared_ptr undoStack = undoStackW_.lock(); if (undoStack.get() != NULL) + { undoStack->Undo(); + } else { - LOG(ERROR) << "Internal error: no undo stack in the viewport controller!"; + LOG(ERROR) << "Internal error: no undo stack!"; } } @@ -80,10 +107,12 @@ { boost::shared_ptr undoStack = undoStackW_.lock(); if (undoStack.get() != NULL) + { undoStack->Redo(); + } else { - LOG(ERROR) << "Internal error: no undo stack in the viewport controller!"; + LOG(ERROR) << "Internal error: no undo stack!"; } } @@ -91,10 +120,12 @@ { boost::shared_ptr undoStack = undoStackW_.lock(); if (undoStack.get() != NULL) + { return undoStack->CanUndo(); + } else { - LOG(ERROR) << "Internal error: no undo stack in the viewport controller!"; + LOG(ERROR) << "Internal error: no undo stack!"; return false; } } @@ -103,21 +134,18 @@ { boost::shared_ptr undoStack = undoStackW_.lock(); if (undoStack.get() != NULL) + { return undoStack->CanRedo(); + } else { - LOG(ERROR) << "Internal error: no undo stack in the viewport controller!"; + LOG(ERROR) << "Internal error: no undo stack!"; return false; } } - bool ViewportController::HandlePointerEvent(PointerEvent e) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - std::vector > ViewportController::HitTestMeasureTools( - ScenePoint2D p) + std::vector > + ViewportController::HitTestMeasureTools(ScenePoint2D p) { std::vector > ret; @@ -128,8 +156,7 @@ } return ret; } - - + void ViewportController::ResetMeasuringToolsHighlight() { for (size_t i = 0; i < measureTools_.size(); ++i) @@ -138,68 +165,59 @@ } } - const OrthancStone::AffineTransform2D& ViewportController::GetCanvasToSceneTransform() const + OrthancStone::AffineTransform2D + ViewportController::GetCanvasToSceneTransform() const { - return GetScene().GetCanvasToSceneTransform(); + return scene_->GetCanvasToSceneTransform(); } - const OrthancStone::AffineTransform2D& ViewportController::GetSceneToCanvasTransform() const + OrthancStone::AffineTransform2D + ViewportController::GetSceneToCanvasTransform() const { - return GetScene().GetSceneToCanvasTransform(); + return scene_->GetSceneToCanvasTransform(); } void ViewportController::SetSceneToCanvasTransform( const AffineTransform2D& transform) { - viewport_.GetScene().SetSceneToCanvasTransform(transform); + scene_->SetSceneToCanvasTransform(transform); + + canvasToSceneFactor_ = scene_->GetCanvasToSceneTransform().ComputeZoom(); BroadcastMessage(SceneTransformChanged(*this)); - - // update the canvas to scene factor - canvasToSceneFactor_ = 0.0; - canvasToSceneFactor_ = GetCanvasToSceneFactor(); } - void ViewportController::FitContent( - unsigned int canvasWidth, unsigned int canvasHeight) + void ViewportController::FitContent(unsigned int viewportWidth, + unsigned int viewportHeight) { - viewport_.GetScene().FitContent(canvasWidth, canvasHeight); + scene_->FitContent(viewportWidth, viewportHeight); + canvasToSceneFactor_ = scene_->GetCanvasToSceneTransform().ComputeZoom(); BroadcastMessage(SceneTransformChanged(*this)); } - void ViewportController::FitContent() + void ViewportController::AddMeasureTool( + boost::shared_ptr measureTool) { - if (viewport_.HasCompositor()) - { - const ICompositor& compositor = viewport_.GetCompositor(); - viewport_.GetScene().FitContent(compositor.GetCanvasWidth(), compositor.GetCanvasHeight()); - BroadcastMessage(SceneTransformChanged(*this)); - } - } - - void ViewportController::AddMeasureTool(boost::shared_ptr measureTool) - { - ORTHANC_ASSERT(std::find(measureTools_.begin(), measureTools_.end(), measureTool) - == measureTools_.end(), "Duplicate measure tool"); + ORTHANC_ASSERT(std::find(measureTools_.begin(), + measureTools_.end(), + measureTool) == measureTools_.end(), + "Duplicate measure tool"); measureTools_.push_back(measureTool); } - void ViewportController::RemoveMeasureTool(boost::shared_ptr measureTool) + void ViewportController::RemoveMeasureTool( + boost::shared_ptr measureTool) { - ORTHANC_ASSERT(std::find(measureTools_.begin(), measureTools_.end(), measureTool) - != measureTools_.end(), "Measure tool not found"); + ORTHANC_ASSERT(std::find(measureTools_.begin(), + measureTools_.end(), + measureTool) != measureTools_.end(), + "Measure tool not found"); measureTools_.erase( std::remove(measureTools_.begin(), measureTools_.end(), measureTool), measureTools_.end()); } - double ViewportController::GetCanvasToSceneFactor() const { - if (canvasToSceneFactor_ == 0) - { - canvasToSceneFactor_ = - GetScene().GetCanvasToSceneTransform().ComputeZoom(); - } return canvasToSceneFactor_; } @@ -222,4 +240,68 @@ { return TEXT_CENTER_DISTANCE_CANVAS_COORD * GetCanvasToSceneFactor(); } + + + void ViewportController::HandleMousePress( + OrthancStone::IViewportInteractor& interactor, + const PointerEvent& event, + unsigned int viewportWidth, + unsigned int viewportHeight) + { + if (activeTracker_) + { + // We are dealing with a multi-stage tracker (that is made of several + // interactions) + activeTracker_->PointerDown(event); + + if (!activeTracker_->IsAlive()) + { + activeTracker_.reset(); + } + } + else + { + // Check whether there is already a measure tool at that position + for (size_t i = 0; i < measureTools_.size(); ++i) + { + if (measureTools_[i]->HitTest(event.GetMainPosition())) + { + activeTracker_ = measureTools_[i]->CreateEditionTracker(event); + return; + } + } + + // No measure tool, create new tracker from the interactor + activeTracker_.reset(interactor.CreateTracker(viewport_, + event, + viewportWidth, + viewportHeight)); + } + } + + bool ViewportController::HandleMouseMove(const PointerEvent& event) + { + if (activeTracker_) + { + activeTracker_->PointerMove(event); + return true; + } + else + { + return false; + } + } + + void ViewportController::HandleMouseRelease(const PointerEvent& event) + { + if (activeTracker_) + { + activeTracker_->PointerUp(event); + + if (!activeTracker_->IsAlive()) + { + activeTracker_.reset(); + } + } + } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Scene2DViewport/ViewportController.h --- a/Framework/Scene2DViewport/ViewportController.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Scene2DViewport/ViewportController.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,14 +22,43 @@ #include "PredeclaredTypes.h" -#include "../Viewport/IViewport.h" -#include "../Scene2D/PointerEvent.h" +#include "../Messages/IObservable.h" +#include "../Scene2D/Scene2D.h" #include "../Scene2DViewport/IFlexiblePointerTracker.h" +#include + +#include #include namespace OrthancStone { + // TODO - Move this to another file + class IViewportInteractor : public boost::noncopyable + { + public: + virtual ~IViewportInteractor() + { + } + + virtual IFlexiblePointerTracker* CreateTracker(boost::shared_ptr viewport, + const PointerEvent& event, + unsigned int viewportWidth, + unsigned int viewportHeight) = 0; + }; + + + // TODO - Move this to another file + class DefaultViewportInteractor : public IViewportInteractor + { + public: + virtual IFlexiblePointerTracker* CreateTracker(boost::shared_ptr viewport, + const PointerEvent& event, + unsigned int viewportWidth, + unsigned int viewportHeight) ORTHANC_OVERRIDE; + }; + + class UndoStack; const double ARC_RADIUS_CANVAS_COORD = 30.0; @@ -74,31 +103,26 @@ Each canvas or other GUI area where we want to display a 2D image, either directly or through slicing must be assigned a ViewportController. */ - class ViewportController : public IObservable + class ViewportController : + public IObservable, + public boost::enable_shared_from_this { public: ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, \ - SceneTransformChanged, ViewportController); + SceneTransformChanged, \ + ViewportController); - ViewportController(boost::weak_ptr undoStackW, - MessageBroker& broker, - IViewport& viewport); - + ViewportController(boost::shared_ptr viewport); ~ViewportController(); - /** - This method is called by the GUI system and should update/delete the - current tracker - */ - bool HandlePointerEvent(PointerEvent e); - /** This method returns the list of measure tools containing the supplied point (in scene coords). A tracker can then be requested from the chosen measure tool, if needed */ - std::vector > HitTestMeasureTools(ScenePoint2D p); + std::vector > HitTestMeasureTools( + ScenePoint2D p); /** This function will traverse the measuring tools and will clear their @@ -110,20 +134,20 @@ With this method, the object takes ownership of the supplied tracker and updates it according to user interaction */ - void SetActiveTracker(boost::shared_ptr tracker); + void AcquireActiveTracker(IFlexiblePointerTracker* tracker); /** Forwarded to the underlying scene */ - const AffineTransform2D& GetCanvasToSceneTransform() const; + AffineTransform2D GetCanvasToSceneTransform() const; /** Forwarded to the underlying scene */ - const AffineTransform2D& GetSceneToCanvasTransform() const; + AffineTransform2D GetSceneToCanvasTransform() const; /** Forwarded to the underlying scene, and broadcasted to the observers */ void SetSceneToCanvasTransform(const AffineTransform2D& transform); /** Forwarded to the underlying scene, and broadcasted to the observers */ - void FitContent(unsigned int canvasWidth, unsigned int canvasHeight); - void FitContent(); + void FitContent(unsigned int viewportWidth, + unsigned int viewportHeight); /** Adds a new measure tool */ void AddMeasureTool(boost::shared_ptr measureTool); @@ -173,31 +197,74 @@ /** forwarded to the UndoStack */ bool CanRedo() const; - Scene2D& GetScene() - { - return viewport_.GetScene(); - } + + // Must be expressed in canvas coordinates + void HandleMousePress(IViewportInteractor& interactor, + const PointerEvent& event, + unsigned int viewportWidth, + unsigned int viewportHeight); + + // Must be expressed in canvas coordinates. Returns "true" if the + // state has changed, so that "Invalidate()" can be called. + bool HandleMouseMove(const PointerEvent& event); + + // Must be expressed in canvas coordinates + void HandleMouseRelease(const PointerEvent& event); const Scene2D& GetScene() const { - return const_cast(viewport_).GetScene(); + return *scene_; + } + + Scene2D& GetScene() + { + return *scene_; + } + + /** + This method is used in a move pattern: when the ownership of the scene + managed by this viewport controller must be transferred to another + controller. + */ + Scene2D* ReleaseScene() + { + return scene_.release(); + } + + /** + This method is used when one wishes to replace the scene that is currently + managed by the controller. The previous scene is deleted and the controller + now has ownership of the new one. + */ + void AcquireScene(Scene2D* scene) + { + scene_.reset(scene); + } + + /** + Sets the undo stack that is used by PushCommand, Undo... + */ + void SetUndoStack(boost::weak_ptr undoStackW) + { + undoStackW_ = undoStackW; + } + + bool HasActiveTracker() const + { + return activeTracker_.get() != NULL; } private: double GetCanvasToSceneFactor() const; - boost::weak_ptr undoStackW_; - - boost::shared_ptr GetUndoStack(); - boost::shared_ptr GetUndoStack() const; + boost::shared_ptr viewport_; + boost::weak_ptr undoStackW_; // Global stack, possibly shared by all viewports + std::vector > measureTools_; + boost::shared_ptr activeTracker_; // TODO - Couldn't this be a "std::unique_ptr"? - std::vector > measureTools_; - boost::shared_ptr tracker_; - + std::unique_ptr scene_; + // this is cached - mutable double canvasToSceneFactor_; - - // Refactoring on 2019-07-10: Removing shared_ptr from scene - IViewport& viewport_; + double canvasToSceneFactor_; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/StoneEnumerations.h --- a/Framework/StoneEnumerations.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/StoneEnumerations.h Wed Apr 22 14:05:47 2020 +0200 @@ -24,21 +24,6 @@ #include -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 @@ -59,7 +44,8 @@ { MouseButton_Left, MouseButton_Right, - MouseButton_Middle + MouseButton_Middle, + MouseButton_None // For instance, because of touch event }; enum MouseWheelDirection @@ -135,6 +121,15 @@ BitmapAnchor_TopRight }; + enum SliceAction + { + SliceAction_FastPlus, + SliceAction_Plus, + SliceAction_None, + SliceAction_Minus, + SliceAction_FastMinus + }; + SopClassUid StringToSopClassUid(const std::string& source); void ComputeWindowing(float& targetCenter, diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/StoneInitialization.cpp --- a/Framework/StoneInitialization.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/StoneInitialization.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,20 +21,58 @@ #include "StoneInitialization.h" -#include - #if !defined(ORTHANC_ENABLE_SDL) # error Macro ORTHANC_ENABLE_SDL must be defined #endif +#if !defined(ORTHANC_ENABLE_QT) +# error Macro ORTHANC_ENABLE_QT must be defined +#endif + +#if !defined(ORTHANC_ENABLE_SSL) +# error Macro ORTHANC_ENABLE_SSL must be defined +#endif + +#if !defined(ORTHANC_ENABLE_CURL) +# error Macro ORTHANC_ENABLE_CURL must be defined +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK) +# error Macro ORTHANC_ENABLE_DCMTK must be defined +# if !defined(DCMTK_VERSION_NUMBER) +# error Macro DCMTK_VERSION_NUMBER must be defined +# endif +#endif + #if ORTHANC_ENABLE_SDL == 1 # include "Viewport/SdlWindow.h" #endif +#if ORTHANC_ENABLE_QT == 1 +# include +#endif + #if ORTHANC_ENABLE_CURL == 1 -#include +# include +#endif + +#if ORTHANC_ENABLE_DCMTK == 1 +# include #endif +#if ORTHANC_ENABLE_WASM == 1 +static double viewportsTimeout_ = 1000; +static std::unique_ptr viewportsRegistry_; +#endif + +#include "Toolbox/LinearAlgebra.h" + +#include +#include + +#include + + namespace OrthancStone { #if ORTHANC_ENABLE_LOGGING_PLUGIN == 1 @@ -49,25 +87,142 @@ Orthanc::Logging::Initialize(); #endif -#if ORTHANC_ENABLE_SDL == 1 - OrthancStone::SdlWindow::GlobalInitialize(); +#if ORTHANC_ENABLE_SSL == 1 + // Must be before curl + Orthanc::Toolbox::InitializeOpenSsl(); #endif #if ORTHANC_ENABLE_CURL == 1 Orthanc::HttpClient::GlobalInitialize(); +# if ORTHANC_ENABLE_SSL == 1 + Orthanc::HttpClient::ConfigureSsl(false, ""); +# endif +#endif + +#if ORTHANC_ENABLE_DCMTK == 1 + Orthanc::FromDcmtkBridge::InitializeDictionary(true); + Orthanc::FromDcmtkBridge::InitializeCodecs(); +# if DCMTK_VERSION_NUMBER <= 360 + OFLog::configure(OFLogger::FATAL_LOG_LEVEL); +# else + OFLog::configure(OFLogger::OFF_LOG_LEVEL); +# endif +#endif + + /** + * This call is necessary to make "boost::lexical_cast<>" work in + * a consistent way in the presence of "double" or "float", and of + * a numeric locale that replaces dot (".") by comma (",") as the + * decimal separator. + * https://stackoverflow.com/a/18981514/881731 + **/ + std::locale::global(std::locale::classic()); + + { + // Run-time checks of locale settings, to be run after Qt has + // been initialized, as Qt changes locale settings + +#if ORTHANC_ENABLE_QT == 1 + if (QCoreApplication::instance() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "Qt must be initialized before Stone"); + } +#endif + + { + OrthancStone::Vector v; + if (!OrthancStone::LinearAlgebra::ParseVector(v, "1.3671875\\-1.3671875") || + v.size() != 2 || + !OrthancStone::LinearAlgebra::IsNear(1.3671875f, v[0]) || + !OrthancStone::LinearAlgebra::IsNear(-1.3671875f, v[1])) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Error in the locale settings, giving up"); + } + } + + { + Json::Value dicomweb = Json::objectValue; + dicomweb["00280030"] = Json::objectValue; + dicomweb["00280030"]["vr"] = "DS"; + dicomweb["00280030"]["Value"] = Json::arrayValue; + dicomweb["00280030"]["Value"].append(1.2f); + dicomweb["00280030"]["Value"].append(-1.5f); + + Orthanc::DicomMap source; + source.FromDicomWeb(dicomweb); + + std::string s; + OrthancStone::Vector v; + if (!source.LookupStringValue(s, Orthanc::DICOM_TAG_PIXEL_SPACING, false) || + !OrthancStone::LinearAlgebra::ParseVector(v, s) || + v.size() != 2 || + !OrthancStone::LinearAlgebra::IsNear(1.2f, v[0]) || + !OrthancStone::LinearAlgebra::IsNear(-1.5f, v[1])) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Error in the locale settings, giving up"); + } + } + } + +#if ORTHANC_ENABLE_SDL == 1 + OrthancStone::SdlWindow::GlobalInitialize(); #endif } + void StoneFinalize() { +#if ORTHANC_ENABLE_WASM == 1 + viewportsRegistry_.reset(); +#endif + #if ORTHANC_ENABLE_SDL == 1 OrthancStone::SdlWindow::GlobalFinalize(); #endif +#if ORTHANC_ENABLE_DCMTK == 1 + Orthanc::FromDcmtkBridge::FinalizeCodecs(); +#endif + #if ORTHANC_ENABLE_CURL == 1 Orthanc::HttpClient::GlobalFinalize(); #endif +#if ORTHANC_ENABLE_SSL == 1 + Orthanc::Toolbox::FinalizeOpenSsl(); +#endif + Orthanc::Logging::Finalize(); } + + +#if ORTHANC_ENABLE_WASM == 1 + void SetWebGLViewportsRegistryTimeout(double timeout) + { + if (viewportsRegistry_.get()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + viewportsTimeout_ = timeout; + } + } +#endif + + +#if ORTHANC_ENABLE_WASM == 1 + WebGLViewportsRegistry& GetWebGLViewportsRegistry() + { + if (viewportsRegistry_.get() == NULL) + { + viewportsRegistry_.reset(new WebGLViewportsRegistry(viewportsTimeout_)); + } + + return *viewportsRegistry_; + } +#endif } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/StoneInitialization.h --- a/Framework/StoneInitialization.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/StoneInitialization.h Wed Apr 22 14:05:47 2020 +0200 @@ -21,6 +21,18 @@ #pragma once +#if !defined(ORTHANC_ENABLE_WASM) +# error Macro ORTHANC_ENABLE_WASM must be defined +#endif + +#if !defined(ORTHANC_ENABLE_LOGGING_PLUGIN) +# error Macro ORTHANC_ENABLE_LOGGING_PLUGIN must be defined +#endif + +#if ORTHANC_ENABLE_WASM == 1 +# include "Viewport/WebGLViewportsRegistry.h" +#endif + #include namespace OrthancStone @@ -32,4 +44,12 @@ #endif void StoneFinalize(); + +#if ORTHANC_ENABLE_WASM == 1 + void SetWebGLViewportsRegistryTimeout(double timeout); +#endif + +#if ORTHANC_ENABLE_WASM == 1 + WebGLViewportsRegistry& GetWebGLViewportsRegistry(); +#endif } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/AffineTransform2D.cpp diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/AffineTransform2D.h diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/CoordinateSystem3D.cpp --- a/Framework/Toolbox/CoordinateSystem3D.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -241,4 +241,14 @@ return s; } + + CoordinateSystem3D CoordinateSystem3D::NormalizeCuttingPlane(const CoordinateSystem3D& plane) + { + double ox, oy; + plane.ProjectPoint(ox, oy, LinearAlgebra::CreateVector(0, 0, 0)); + + CoordinateSystem3D normalized(plane); + normalized.SetOrigin(plane.MapSliceToWorldCoordinates(ox, oy)); + return normalized; + } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/CoordinateSystem3D.h --- a/Framework/Toolbox/CoordinateSystem3D.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.h Wed Apr 22 14:05:47 2020 +0200 @@ -22,6 +22,7 @@ #pragma once #include "LinearAlgebra.h" +#include "../Scene2D/ScenePoint2D.h" #include @@ -95,12 +96,24 @@ Vector MapSliceToWorldCoordinates(double x, double y) const; + Vector MapSliceToWorldCoordinates(const ScenePoint2D& p) const + { + return MapSliceToWorldCoordinates(p.GetX(), p.GetY()); + } + double ProjectAlongNormal(const Vector& point) const; void ProjectPoint(double& offsetX, double& offsetY, const Vector& point) const; + ScenePoint2D ProjectPoint(const Vector& point) const + { + double x, y; + ProjectPoint(x, y, point); + return ScenePoint2D(x, y); + } + /* Alternated faster implementation (untested yet) */ @@ -120,5 +133,9 @@ static bool ComputeDistance(double& distance, const CoordinateSystem3D& a, const CoordinateSystem3D& b); + + // Normalize a cutting plane so that the origin (0,0,0) of the 3D + // world is mapped to the origin of its (x,y) coordinate system + static CoordinateSystem3D NormalizeCuttingPlane(const CoordinateSystem3D& plane); }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/DicomInstanceParameters.cpp --- a/Framework/Toolbox/DicomInstanceParameters.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Toolbox/DicomInstanceParameters.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -265,7 +265,7 @@ } void DicomInstanceParameters::Data::ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image, - bool useDouble) const + bool useDouble) const { if (image.GetFormat() != Orthanc::PixelFormat_Float32) { @@ -387,6 +387,21 @@ TextureBaseSceneLayer* DicomInstanceParameters::CreateTexture (const Orthanc::ImageAccessor& pixelData) const { + // { + // const Orthanc::ImageAccessor& source = pixelData; + // const void* sourceBuffer = source.GetConstBuffer(); + // intptr_t sourceBufferInt = reinterpret_cast(sourceBuffer); + // int sourceWidth = source.GetWidth(); + // int sourceHeight = source.GetHeight(); + // int sourcePitch = source.GetPitch(); + + // // TODO: turn error into trace below + // LOG(ERROR) << "ConvertGrayscaleToFloat | source:" + // << " W = " << sourceWidth << " H = " << sourceHeight + // << " P = " << sourcePitch << " B = " << sourceBufferInt + // << " B % 4 == " << sourceBufferInt % 4; + // } + assert(sizeof(float) == 4); Orthanc::PixelFormat sourceFormat = pixelData.GetFormat(); @@ -468,4 +483,48 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } } + + + double DicomInstanceParameters::Data::ApplyRescale(double value) const + { + double factor = doseGridScaling_; + double offset = 0.0; + + if (hasRescale_) + { + factor *= rescaleSlope_; + offset = rescaleIntercept_; + } + + return (value * factor + offset); + } + + + bool DicomInstanceParameters::Data::ComputeRegularSpacing(double& spacing) const + { + if (frameOffsets_.size() == 0) // Not a RT-DOSE + { + return false; + } + else if (frameOffsets_.size() == 1) + { + spacing = 1; // Edge case: RT-DOSE with one single frame + return true; + } + else + { + spacing = std::abs(frameOffsets_[1] - frameOffsets_[0]); + + for (size_t i = 1; i + 1 < frameOffsets_.size(); i++) + { + double s = frameOffsets_[i + 1] - frameOffsets_[i]; + if (!LinearAlgebra::IsNear(spacing, s, 0.001)) + { + return false; + } + } + + return true; + } + } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/DicomInstanceParameters.h --- a/Framework/Toolbox/DicomInstanceParameters.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Toolbox/DicomInstanceParameters.h Wed Apr 22 14:05:47 2020 +0200 @@ -71,8 +71,12 @@ bool IsPlaneWithinSlice(unsigned int frame, const CoordinateSystem3D& plane) const; - void ApplyRescaleAndDoseScaling( - Orthanc::ImageAccessor& image, bool useDouble) const; + void ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image, + bool useDouble) const; + + double ApplyRescale(double value) const; + + bool ComputeRegularSpacing(double& target) const; }; @@ -207,9 +211,25 @@ return data_.doseUnits_; } + void SetDoseGridScaling(double value) + { + data_.doseGridScaling_ = value; + } + double GetDoseGridScaling() const { return data_.doseGridScaling_; } + + double ApplyRescale(double value) const + { + return data_.ApplyRescale(value); + } + + // Required for RT-DOSE + bool ComputeRegularSpacing(double& target) const + { + return data_.ComputeRegularSpacing(target); + } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/DicomStructureSet.cpp --- a/Framework/Toolbox/DicomStructureSet.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Toolbox/DicomStructureSet.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -27,12 +27,11 @@ #include #include #include -#include #include #if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4244) +# pragma warning(push) +# pragma warning(disable:4244) #endif #include @@ -43,9 +42,14 @@ #include #if defined(_MSC_VER) -#pragma warning(pop) +# pragma warning(pop) #endif +#if ORTHANC_ENABLE_DCMTK == 1 +# include "ParsedDicomDataset.h" +#endif + + typedef boost::geometry::model::d2::point_xy BoostPoint; typedef boost::geometry::model::polygon BoostPolygon; typedef boost::geometry::model::multi_polygon BoostMultiPolygon; @@ -81,7 +85,7 @@ } } -#ifdef USE_BOOST_UNION_FOR_POLYGONS +#if USE_BOOST_UNION_FOR_POLYGONS == 1 static BoostPolygon CreateRectangle(float x1, float y1, float x2, float y2) @@ -99,7 +103,7 @@ namespace OrthancStone { static RtStructRectangleInSlab CreateRectangle(float x1, float y1, - float x2, float y2) + float x2, float y2) { RtStructRectangleInSlab rect; rect.xmin = std::min(x1, x2); @@ -174,15 +178,15 @@ double magnitude = GeometryToolbox::ProjectAlongNormal(v, geometry_.GetNormal()); if(!LinearAlgebra::IsNear( - magnitude, - projectionAlongNormal_, - sliceThickness_ / 2.0 /* in mm */ )) + magnitude, + projectionAlongNormal_, + sliceThickness_ / 2.0 /* in mm */ )) { LOG(ERROR) << "This RT-STRUCT contains a point that is off the " - << "slice of its instance | " - << "magnitude = " << magnitude << " | " - << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | " - << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0); + << "slice of its instance | " + << "magnitude = " << magnitude << " | " + << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | " + << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0); throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } @@ -202,10 +206,10 @@ if (!onSlice) { LOG(WARNING) << "This RT-STRUCT contains a point that is off the " - << "slice of its instance | " - << "magnitude = " << magnitude << " | " - << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | " - << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0); + << "slice of its instance | " + << "magnitude = " << magnitude << " | " + << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | " + << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0); } return onSlice; } @@ -373,12 +377,14 @@ else if (GeometryToolbox::IsParallelOrOpposite (isOpposite, slice.GetNormal(), geometry_.GetAxisX())) { - // plane is constant X + // plane is constant X => Sagittal view (remember that in the + // sagittal projection, the normal must be swapped) + /* - Please read the comments in the section above, by taking into account - the fact that, in this case, the plane has a constant X, not Y (in - polygon geometry_ coordinates) + Please read the comments in the section above, by taking into account + the fact that, in this case, the plane has a constant X, not Y (in + polygon geometry_ coordinates) */ if (x < extent_.GetX1() || @@ -427,10 +433,6 @@ slice.ProjectPoint2(x1, y1, p1); slice.ProjectPoint2(x2, y2, p2); - // TODO WHY THIS??? - y1 = -y1; - y2 = -y2; - return true; } } @@ -463,7 +465,7 @@ return structures_[index]; } - DicomStructureSet::DicomStructureSet(const OrthancPlugins::FullOrthancDataset& tags) + void DicomStructureSet::Setup(const OrthancPlugins::IDicomDataset& tags) { OrthancPlugins::DicomDatasetReader reader(tags); @@ -514,11 +516,11 @@ } LOG(INFO) << "New RT structure: \"" << structures_[i].name_ - << "\" with interpretation \"" << structures_[i].interpretation_ - << "\" containing " << countSlices << " slices (color: " - << static_cast(structures_[i].red_) << "," - << static_cast(structures_[i].green_) << "," - << static_cast(structures_[i].blue_) << ")"; + << "\" with interpretation \"" << structures_[i].interpretation_ + << "\" containing " << countSlices << " slices (color: " + << static_cast(structures_[i].red_) << "," + << static_cast(structures_[i].green_) << "," + << static_cast(structures_[i].blue_) << ")"; // These temporary variables avoid allocating many vectors in the loop below OrthancPlugins::DicomPath countPointsPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, @@ -609,6 +611,15 @@ } +#if ORTHANC_ENABLE_DCMTK == 1 + DicomStructureSet::DicomStructureSet(Orthanc::ParsedDicomFile& instance) + { + ParsedDicomDataset dataset(instance); + Setup(dataset); + } +#endif + + Vector DicomStructureSet::GetStructureCenter(size_t index) const { const Structure& structure = GetStructure(index); @@ -773,14 +784,14 @@ if (Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "") { LOG(ERROR) << "DicomStructureSet::CheckReferencedSlices(): " - << " missing information about referenced instance " - << "(sopInstanceUid is empty!)"; + << " missing information about referenced instance " + << "(sopInstanceUid is empty!)"; } else { LOG(ERROR) << "DicomStructureSet::CheckReferencedSlices(): " - << " missing information about referenced instance " - << "(sopInstanceUid = " << sopInstanceUid << ")"; + << " missing information about referenced instance " + << "(sopInstanceUid = " << sopInstanceUid << ")"; } //throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } @@ -803,17 +814,18 @@ } } -#ifdef USE_BOOST_UNION_FOR_POLYGONS - bool DicomStructureSet::ProjectStructure(std::vector< std::vector >& polygons, - const Structure& structure, - const CoordinateSystem3D& slice) const + bool DicomStructureSet::ProjectStructure( +#if USE_BOOST_UNION_FOR_POLYGONS == 1 + std::vector< std::vector >& polygons, #else - bool DicomStructureSet::ProjectStructure(std::vector< std::pair >& segments, + std::vector< std::pair >& segments, +#endif const Structure& structure, - const CoordinateSystem3D& slice) const -#endif + const CoordinateSystem3D& sourceSlice) const { -#ifdef USE_BOOST_UNION_FOR_POLYGONS + const CoordinateSystem3D slice = CoordinateSystem3D::NormalizeCuttingPlane(sourceSlice); + +#if USE_BOOST_UNION_FOR_POLYGONS == 1 polygons.clear(); #else segments.clear(); @@ -831,7 +843,7 @@ { if (polygon->IsOnSlice(slice)) { -#ifdef USE_BOOST_UNION_FOR_POLYGONS +#if USE_BOOST_UNION_FOR_POLYGONS == 1 polygons.push_back(std::vector()); for (Points::const_iterator p = polygon->GetPoints().begin(); @@ -882,15 +894,26 @@ #if 1 // Sagittal or coronal projection -#ifdef USE_BOOST_UNION_FOR_POLYGONS +#if USE_BOOST_UNION_FOR_POLYGONS == 1 std::vector projected; + + for (Polygons::const_iterator polygon = structure.polygons_.begin(); + polygon != structure.polygons_.end(); ++polygon) + { + double x1, y1, x2, y2; + + if (polygon->Project(x1, y1, x2, y2, slice)) + { + projected.push_back(CreateRectangle(x1, y1, x2, y2)); + } + } #else // this will contain the intersection of the polygon slab with // the cutting plane, projected on the cutting plane coord system // (that yields a rectangle) + the Z coordinate of the polygon // (this is required to group polygons with the same Z later) std::vector > projected; -#endif + for (Polygons::const_iterator polygon = structure.polygons_.begin(); polygon != structure.polygons_.end(); ++polygon) { @@ -903,13 +926,15 @@ // x1,y1 and x2,y2 are in "slice" coordinates (the cutting plane // geometry) projected.push_back(std::make_pair(CreateRectangle( - static_cast(x1), - static_cast(y1), - static_cast(x2), - static_cast(y2)),curZ)); + static_cast(x1), + static_cast(y1), + static_cast(x2), + static_cast(y2)),curZ)); } } -#ifndef USE_BOOST_UNION_FOR_POLYGONS +#endif + +#if USE_BOOST_UNION_FOR_POLYGONS != 1 // projected contains a set of rectangles specified by two opposite // corners (x1,y1,x2,y2) // we need to merge them @@ -999,4 +1024,44 @@ return false; } } + + + void DicomStructureSet::ProjectOntoLayer(PolylineSceneLayer& layer, + const CoordinateSystem3D& plane, + size_t structureIndex, + const Color& color) const + { +#if USE_BOOST_UNION_FOR_POLYGONS == 1 + std::vector< std::vector > polygons; + if (ProjectStructure(polygons, structureIndex, plane)) + { + for (size_t j = 0; j < polygons.size(); j++) + { + std::vector chain; + chain.reserve(polygons[j].size()); + + for (size_t k = 0; k < polygons[j].size(); k++) + { + chain.push_back(ScenePoint2D(polygons[j][k].x, polygons[j][k].y)); + } + + layer.AddChain(chain, true, color.GetRed(), color.GetGreen(), color.GetBlue()); + } + } + +#else + std::vector< std::pair > segments; + + if (ProjectStructure(segments, structureIndex, plane)) + { + for (size_t j = 0; j < segments.size(); j++) + { + std::vector chain(2); + chain[0] = ScenePoint2D(segments[j].first.x, segments[j].first.y); + chain[1] = ScenePoint2D(segments[j].second.x, segments[j].second.y); + layer.AddChain(chain, false, color.GetRed(), color.GetGreen(), color.GetBlue()); + } + } +#endif + } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/DicomStructureSet.h --- a/Framework/Toolbox/DicomStructureSet.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Toolbox/DicomStructureSet.h Wed Apr 22 14:05:47 2020 +0200 @@ -21,10 +21,19 @@ #pragma once +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + #include "DicomStructureSetUtils.h" #include "CoordinateSystem3D.h" #include "Extent2D.h" #include "../Scene2D/Color.h" +#include "../Scene2D/PolylineSceneLayer.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include +#endif //#define USE_BOOST_UNION_FOR_POLYGONS 1 @@ -137,21 +146,30 @@ Structures structures_; ReferencedSlices referencedSlices_; + void Setup(const OrthancPlugins::IDicomDataset& dataset); + const Structure& GetStructure(size_t index) const; Structure& GetStructure(size_t index); -#ifdef USE_BOOST_UNION_FOR_POLYGONS - bool ProjectStructure(std::vector< std::vector >& polygons, - const Structure& structure, - const CoordinateSystem3D& slice) const; + bool ProjectStructure( +#if USE_BOOST_UNION_FOR_POLYGONS == 1 + std::vector< std::vector >& polygons, #else - bool ProjectStructure(std::vector< std::pair >& segments, + std::vector< std::pair >& segments, +#endif const Structure& structure, const CoordinateSystem3D& slice) const; -#endif + public: - DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance); + DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance) + { + Setup(instance); + } + +#if ORTHANC_ENABLE_DCMTK == 1 + DicomStructureSet(Orthanc::ParsedDicomFile& instance); +#endif size_t GetStructuresCount() const { @@ -185,20 +203,32 @@ Vector GetNormal() const; -#ifdef USE_BOOST_UNION_FOR_POLYGONS +#if USE_BOOST_UNION_FOR_POLYGONS == 1 bool ProjectStructure(std::vector< std::vector >& polygons, - size_t index, - const CoordinateSystem3D& slice) const + size_t index, + const CoordinateSystem3D& slice) const { return ProjectStructure(polygons, GetStructure(index), slice); } #else bool ProjectStructure(std::vector< std::pair >& segments, - size_t index, - const CoordinateSystem3D& slice) const + size_t index, + const CoordinateSystem3D& slice) const { return ProjectStructure(segments, GetStructure(index), slice); } #endif + + void ProjectOntoLayer(PolylineSceneLayer& layer, + const CoordinateSystem3D& plane, + size_t structureIndex, + const Color& color) const; + + void ProjectOntoLayer(PolylineSceneLayer& layer, + const CoordinateSystem3D& plane, + size_t structureIndex) const + { + ProjectOntoLayer(layer, plane, structureIndex, GetStructureColor(structureIndex)); + } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/GenericToolbox.h --- a/Framework/Toolbox/GenericToolbox.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Toolbox/GenericToolbox.h Wed Apr 22 14:05:47 2020 +0200 @@ -20,15 +20,27 @@ #pragma once +#include +#include + +#include + #include #include #include +#include namespace OrthancStone { namespace GenericToolbox { + /** + Fast floating point string validation. + No trimming applied, so the input must match regex + /^[-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/ + The following are allowed as edge cases: "" and "-" + */ inline bool LegitDoubleString(const char* text) { const char* p = text; @@ -70,6 +82,11 @@ return true; } + /** + Fast integer string validation. + No trimming applied, so the input must match regex /^-?[0-9]*$/ + The following are allowed as edge cases: "" and "-" + */ inline bool LegitIntegerString(const char* text) { const char* p = text; @@ -86,6 +103,9 @@ } /* + Fast string --> double conversion. + Must pass the LegitDoubleString test + String to doubles with at most 18 digits */ inline bool StringToDouble(double& r, const char* text) @@ -210,10 +230,17 @@ return StringToDouble(r, text.c_str()); } + /** + Fast string to integer conversion. Leading zeroes and minus are accepted, + but a leading + sign is NOT. + Must pass the LegitIntegerString function test. + In addition, an empty string (or lone minus sign) yields 0. + */ + template inline bool StringToInteger(T& r, const char* text) { - if (!LegitDoubleString(text)) + if (!LegitIntegerString(text)) return false; r = 0; @@ -244,22 +271,36 @@ } /** - "rgb(12,23,255)" --> red, green, blue and returns true - "everything else" --> returns false (other values left untouched) + if input is "rgb(12,23,255)" --> function fills `red`, `green` and `blue` and returns true + else ("everything else") --> function returns false and leaves all values untouched */ bool GetRgbValuesFromString(uint8_t& red, uint8_t& green, uint8_t& blue, const char* text); /** - See other overload + See main overload */ inline bool GetRgbValuesFromString(uint8_t& red, uint8_t& green, uint8_t& blue, const std::string& text) { return GetRgbValuesFromString(red, green, blue, text.c_str()); } - bool GetRgbaValuesFromString(uint8_t& red, uint8_t& green, uint8_t& blue, uint8_t& alpha, const char* text); + /** + Same as GetRgbValuesFromString + */ + bool GetRgbaValuesFromString(uint8_t& red, + uint8_t& green, + uint8_t& blue, + uint8_t& alpha, + const char* text); - inline bool GetRgbaValuesFromString(uint8_t& red, uint8_t& green, uint8_t& blue, uint8_t& alpha, const std::string& text) + /** + Same as GetRgbValuesFromString + */ + inline bool GetRgbaValuesFromString(uint8_t& red, + uint8_t& green, + uint8_t& blue, + uint8_t& alpha, + const std::string& text) { return GetRgbaValuesFromString(red, green, blue, alpha, text.c_str()); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/LinearAlgebra.cpp --- a/Framework/Toolbox/LinearAlgebra.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Toolbox/LinearAlgebra.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -22,6 +22,7 @@ #include "LinearAlgebra.h" #include "../StoneException.h" +#include "GenericToolbox.h" #include #include @@ -32,6 +33,7 @@ #include #include +#include namespace OrthancStone { @@ -74,13 +76,30 @@ for (size_t i = 0; i < items.size(); i++) { /** + * SJO - 2019-11-19 - WARNING: I reverted from "std::stod()" + * to "boost::lexical_cast", as both "std::stod()" and + * "std::strtod()" are sensitive to locale settings, making + * this code non portable and very dangerous as it fails + * silently. A string such as "1.3671875\1.3671875" is + * interpreted as "1\1", because "std::stod()" expects a comma + * (",") instead of a point ("."). This problem is notably + * seen in Qt-based applications, that somehow set locales + * aggressively. + * + * "boost::lexical_cast<>" is also dependent on the locale + * settings, but apparently not in a way that makes this + * function fail with Qt. The Orthanc core defines macro + * "-DBOOST_LEXICAL_CAST_ASSUME_C_LOCALE" in static builds to + * this end. + **/ + +#if 0 // __cplusplus >= 201103L // Is C++11 enabled? + /** * 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/ + * as it is very slow, and as Stone has to parse many doubles. + * https://tinodidriksen.com/2011/05/cpp-convert-string-to-double-speed/ **/ -#if __cplusplus >= 201103L // Is C++11 enabled? try { target[i] = std::stod(items[i]); @@ -90,7 +109,37 @@ target.clear(); return false; } -#else // Fallback implementation using Boost + +#elif 0 + /** + * "std::strtod()" is the recommended alternative to + * "std::stod()". It is apparently as fast as plain-C + * "atof()", with more security. + **/ + char* end = NULL; + target[i] = std::strtod(items[i].c_str(), &end); + if (end == NULL || + end != items[i].c_str() + items[i].size()) + { + return false; + } + +#elif 1 + /** + * Use of our homemade implementation of + * "boost::lexical_cast()". It is much faster than boost. + **/ + if (!GenericToolbox::StringToDouble(target[i], items[i].c_str())) + { + return false; + } + +#else + /** + * Fallback implementation using Boost (slower, but somehow + * independent to locale contrarily to "std::stod()", and + * generic as it does not use our custom implementation). + **/ try { target[i] = boost::lexical_cast(items[i]); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/ParsedDicomCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/ParsedDicomCache.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "ParsedDicomCache.h" + +namespace OrthancStone +{ + class ParsedDicomCache::Item : public Orthanc::ICacheable + { + private: + std::unique_ptr dicom_; + size_t fileSize_; + bool hasPixelData_; + + public: + Item(Orthanc::ParsedDicomFile* dicom, + size_t fileSize, + bool hasPixelData) : + dicom_(dicom), + fileSize_(fileSize), + hasPixelData_(hasPixelData) + { + if (dicom == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + virtual size_t GetMemoryUsage() const + { + return fileSize_; + } + + Orthanc::ParsedDicomFile& GetDicom() const + { + assert(dicom_.get() != NULL); + return *dicom_; + } + + bool HasPixelData() const + { + return hasPixelData_; + } + }; + + + std::string ParsedDicomCache::GetIndex(unsigned int bucket, + const std::string& bucketKey) + { + return boost::lexical_cast(bucket) + "|" + bucketKey; + } + + + void ParsedDicomCache::Acquire(unsigned int bucket, + const std::string& bucketKey, + Orthanc::ParsedDicomFile* dicom, + size_t fileSize, + bool hasPixelData) + { + LOG(TRACE) << "new item stored in cache: bucket " << bucket << ", key " << bucketKey; + cache_.Acquire(GetIndex(bucket, bucketKey), new Item(dicom, fileSize, hasPixelData)); + } + + + ParsedDicomCache::Reader::Reader(ParsedDicomCache& cache, + unsigned int bucket, + const std::string& bucketKey) : + /** + * The "DcmFileFormat" object cannot be accessed from multiple + * threads, even if using only getters. An unique lock (mutex) is + * mandatory. + **/ + accessor_(cache.cache_, GetIndex(bucket, bucketKey), true /* unique */) + { + if (accessor_.IsValid()) + { + LOG(TRACE) << "accessing item within cache: bucket " << bucket << ", key " << bucketKey; + item_ = &dynamic_cast(accessor_.GetValue()); + } + else + { + LOG(TRACE) << "missing item within cache: bucket " << bucket << ", key " << bucketKey; + item_ = NULL; + } + } + + + bool ParsedDicomCache::Reader::HasPixelData() const + { + if (item_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return item_->HasPixelData(); + } + } + + + Orthanc::ParsedDicomFile& ParsedDicomCache::Reader::GetDicom() const + { + if (item_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return item_->GetDicom(); + } + } + + + size_t ParsedDicomCache::Reader::GetFileSize() const + { + if (item_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return item_->GetMemoryUsage(); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/ParsedDicomCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/ParsedDicomCache.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,80 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + +namespace OrthancStone +{ + class ParsedDicomCache : public boost::noncopyable + { + private: + class Item; + + static std::string GetIndex(unsigned int bucket, + const std::string& bucketKey); + + Orthanc::MemoryObjectCache cache_; + + public: + ParsedDicomCache(size_t size) + { + cache_.SetMaximumSize(size); + } + + void Invalidate(unsigned int bucket, + const std::string& bucketKey) + { + cache_.Invalidate(GetIndex(bucket, bucketKey)); + } + + void Acquire(unsigned int bucket, + const std::string& bucketKey, + Orthanc::ParsedDicomFile* dicom, + size_t fileSize, + bool hasPixelData); + + class Reader : public boost::noncopyable + { + private: + Orthanc::MemoryObjectCache::Accessor accessor_; + Item* item_; + + public: + Reader(ParsedDicomCache& cache, + unsigned int bucket, + const std::string& bucketKey); + + bool IsValid() const + { + return item_ != NULL; + } + + bool HasPixelData() const; + + Orthanc::ParsedDicomFile& GetDicom() const; + + size_t GetFileSize() const; + }; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/ParsedDicomDataset.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/ParsedDicomDataset.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,104 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "ParsedDicomDataset.h" + +#include + +namespace OrthancStone +{ + static DcmItem* LookupPath(Orthanc::ParsedDicomFile& dicom, + const OrthancPlugins::DicomPath& path) + { + DcmItem* node = dicom.GetDcmtkObject().getDataset(); + + for (size_t i = 0; i < path.GetPrefixLength(); i++) + { + const OrthancPlugins::DicomTag& tmp = path.GetPrefixTag(i); + DcmTagKey tag(tmp.GetGroup(), tmp.GetElement()); + + DcmSequenceOfItems* sequence = NULL; + if (!node->findAndGetSequence(tag, sequence).good() || + sequence == NULL) + { + return NULL; + } + + unsigned long pos = path.GetPrefixIndex(i); + if (pos >= sequence->card()) + { + return NULL; + } + + node = sequence->getItem(pos); + if (node == NULL) + { + return NULL; + } + } + + return node; + } + + + bool ParsedDicomDataset::GetStringValue(std::string& result, + const OrthancPlugins::DicomPath& path) const + { + DcmItem* node = LookupPath(dicom_, path); + + if (node != NULL) + { + DcmTagKey tag(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement()); + + const char* s = NULL; + if (node->findAndGetString(tag, s).good() && + s != NULL) + { + result.assign(s); + return true; + } + } + + return false; + } + + + bool ParsedDicomDataset::GetSequenceSize(size_t& size, + const OrthancPlugins::DicomPath& path) const + { + DcmItem* node = LookupPath(dicom_, path); + + if (node != NULL) + { + DcmTagKey tag(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement()); + + DcmSequenceOfItems* s = NULL; + if (node->findAndGetSequence(tag, s).good() && + s != NULL) + { + size = s->card(); + return true; + } + } + + return false; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/ParsedDicomDataset.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/ParsedDicomDataset.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,46 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + +namespace OrthancStone +{ + class ParsedDicomDataset : public OrthancPlugins::IDicomDataset + { + private: + Orthanc::ParsedDicomFile& dicom_; + + public: + ParsedDicomDataset(Orthanc::ParsedDicomFile& dicom) : + dicom_(dicom) + { + } + + virtual bool GetStringValue(std::string& result, + const OrthancPlugins::DicomPath& path) const ORTHANC_OVERRIDE; + + virtual bool GetSequenceSize(size_t& size, + const OrthancPlugins::DicomPath& path) const ORTHANC_OVERRIDE; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/SlicesSorter.cpp --- a/Framework/Toolbox/SlicesSorter.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Toolbox/SlicesSorter.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -289,13 +289,14 @@ } - double SlicesSorter::ComputeSpacingBetweenSlices() const + bool SlicesSorter::ComputeSpacingBetweenSlices(double& spacing /* out */) const { if (GetSlicesCount() <= 1) { // This is a volume that is empty or that contains one single // slice: Choose a dummy z-dimension for voxels - return 1.0; + spacing = 1.0; + return true; } const OrthancStone::CoordinateSystem3D& reference = GetSliceGeometry(0); @@ -303,28 +304,27 @@ double referencePosition = reference.ProjectAlongNormal(reference.GetOrigin()); double p = reference.ProjectAlongNormal(GetSliceGeometry(1).GetOrigin()); - double spacingZ = p - referencePosition; + spacing = p - referencePosition; - if (spacingZ <= 0) + if (spacing <= 0) { - LOG(ERROR) << "SlicesSorter::ComputeSpacingBetweenSlices(): (spacingZ <= 0)"; + LOG(ERROR) << "SlicesSorter::ComputeSpacingBetweenSlices(): (spacing <= 0)"; throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, "Please call the Sort() method before"); } for (size_t i = 1; i < GetSlicesCount(); i++) { - OrthancStone::Vector p = reference.GetOrigin() + spacingZ * static_cast(i) * reference.GetNormal(); + OrthancStone::Vector p = reference.GetOrigin() + spacing * static_cast(i) * reference.GetNormal(); double d = boost::numeric::ublas::norm_2(p - GetSliceGeometry(i).GetOrigin()); if (!OrthancStone::LinearAlgebra::IsNear(d, 0, 0.001 /* tolerance expressed in mm */)) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, - "The origins of the slices of a volume image are not regularly spaced"); + return false; } } - return spacingZ; + return true; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Toolbox/SlicesSorter.h --- a/Framework/Toolbox/SlicesSorter.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Toolbox/SlicesSorter.h Wed Apr 22 14:05:47 2020 +0200 @@ -91,7 +91,7 @@ const CoordinateSystem3D& slice) const; // WARNING - The slices must have been sorted before calling this method - double ComputeSpacingBetweenSlices() const; + bool ComputeSpacingBetweenSlices(double& spacing /* out */) const; // WARNING - The slices must have been sorted before calling this method bool AreAllSlicesDistinct() const; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/IViewport.h --- a/Framework/Viewport/IViewport.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Viewport/IViewport.h Wed Apr 22 14:05:47 2020 +0200 @@ -21,34 +21,52 @@ #pragma once #include "../Scene2D/ICompositor.h" -#include "../Scene2D/Scene2D.h" -#include "../Scene2D/ScenePoint2D.h" +#include "../Scene2DViewport/ViewportController.h" namespace OrthancStone { /** * Class that combines a Scene2D with a canvas where to draw the * scene. A call to "Refresh()" will update the content of the - * canvas. + * canvas. A "IViewport" can possibly be accessed from several + * threads depending on the rendering back-end (e.g. in SDL or Qt): + * The "ILock" subclass implements the locking mechanism to modify + * the content of the scene. + * + * NB: The lock must be a "recursive_mutex", as the viewport + * controller can lock it a second time (TODO - Why so?). **/ class IViewport : public boost::noncopyable { public: + class ILock : public boost::noncopyable + { + public: + virtual ~ILock() + { + } + + virtual bool HasCompositor() const = 0; + + /** + Do not store the result! Only access the compositor interface through + the lock. + */ + virtual ICompositor& GetCompositor() = 0; + + /** + Do not store the result! Only access the compositor interface through + the lock. + */ + virtual ViewportController& GetController() = 0; + + virtual void Invalidate() = 0; + }; + virtual ~IViewport() { } - virtual Scene2D& GetScene() = 0; - - virtual void Refresh() = 0; - - virtual ScenePoint2D GetPixelCenterCoordinates(int x, int y) const = 0; - - virtual bool HasCompositor() const = 0; - - virtual ICompositor& GetCompositor() = 0; - - virtual const ICompositor& GetCompositor() const = 0; + virtual ILock* Lock() = 0; }; } - diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/SdlViewport.cpp --- a/Framework/Viewport/SdlViewport.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Viewport/SdlViewport.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -26,40 +26,116 @@ namespace OrthancStone { - SdlOpenGLViewport::SdlOpenGLViewport(const char* title, + ICompositor& SdlViewport::SdlLock::GetCompositor() + { + if (that_.compositor_.get() == NULL) + { + // The derived class should have called "AcquireCompositor()" + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + return *that_.compositor_; + } + } + + + void SdlViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */) + { + if (compositor == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + compositor_.reset(compositor); + } + + SdlViewport::SdlViewport() + { + refreshEvent_ = SDL_RegisterEvents(1); + + if (refreshEvent_ == static_cast(-1)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + void SdlViewport::PostConstructor() + { + controller_ = boost::make_shared(shared_from_this()); + } + + void SdlViewport::SendRefreshEvent() + { + SDL_Event event; + SDL_memset(&event, 0, sizeof(event)); + event.type = refreshEvent_; + SDL_PushEvent(&event); // This function is thread-safe, and can be called from other threads safely. + } + + + SdlOpenGLViewport::SdlOpenGLViewport(const std::string& title, unsigned int width, unsigned int height, bool allowDpiScaling) : - context_(title, width, height, allowDpiScaling) + context_(title.c_str(), width, height, allowDpiScaling) + { + AcquireCompositor(new OpenGLCompositor(context_)); // (*) + } + + boost::shared_ptr SdlOpenGLViewport::Create( + const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) { - compositor_.reset(new OpenGLCompositor(context_, GetScene())); + boost::shared_ptr that = + boost::shared_ptr(new SdlOpenGLViewport(title, width, height, allowDpiScaling)); + that->SdlViewport::PostConstructor(); + return that; + } + + SdlOpenGLViewport::~SdlOpenGLViewport() + { + // Make sure that the "OpenGLCompositor" is destroyed BEFORE the + // "OpenGLContext" it references (*) + ClearCompositor(); } - SdlOpenGLViewport::SdlOpenGLViewport(const char* title, - unsigned int width, - unsigned int height, - boost::shared_ptr& scene, - bool allowDpiScaling) : - SdlViewport(scene), - context_(title, width, height, allowDpiScaling) + + void SdlOpenGLViewport::Paint() { - compositor_.reset(new OpenGLCompositor(context_, GetScene())); + SdlLock lock(*this); + lock.GetCompositor().Refresh(lock.GetController().GetScene()); } - void SdlOpenGLViewport::Refresh() + + void SdlOpenGLViewport::UpdateSize(unsigned int width, unsigned int height) { - compositor_->Refresh(); + // nothing to do in OpenGL, the OpenGLCompositor::UpdateSize will be called automatically + SdlLock lock(*this); + lock.Invalidate(); } + void SdlOpenGLViewport::ToggleMaximize() + { + // No need to call "Invalidate()" here, as "UpdateSize()" will + // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED" + SdlLock lock(*this); + context_.ToggleMaximize(); + } + + + SdlCairoViewport::SdlCairoViewport(const char* title, unsigned int width, unsigned int height, bool allowDpiScaling) : window_(title, width, height, false /* enable OpenGL */, allowDpiScaling), - compositor_(GetScene(), width, height) + sdlSurface_(NULL) { - UpdateSdlSurfaceSize(width, height); + AcquireCompositor(new CairoCompositor(width, height)); } SdlCairoViewport::~SdlCairoViewport() @@ -70,29 +146,66 @@ } } - void SdlCairoViewport::Refresh() + void SdlCairoViewport::Paint() { - compositor_.Refresh(); - window_.Render(sdlSurface_); + SdlLock lock(*this); + + lock.GetCompositor().Refresh(lock.GetController().GetScene()); + CreateSdlSurfaceFromCompositor(dynamic_cast(lock.GetCompositor())); + + if (sdlSurface_ != NULL) + { + window_.Render(sdlSurface_); + } } + void SdlCairoViewport::UpdateSize(unsigned int width, unsigned int height) { - compositor_.UpdateSize(width, height); - UpdateSdlSurfaceSize(width, height); - Refresh(); + SdlLock lock(*this); + dynamic_cast(lock.GetCompositor()).UpdateSize(width, height); + lock.Invalidate(); } - void SdlCairoViewport::UpdateSdlSurfaceSize(unsigned int width, - unsigned int height) + + void SdlCairoViewport::ToggleMaximize() + { + // No need to call "Invalidate()" here, as "UpdateSize()" will + // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED" + SdlLock lock(*this); + window_.ToggleMaximize(); + } + + + // Assumes that the mutex is locked + void SdlCairoViewport::CreateSdlSurfaceFromCompositor(CairoCompositor& compositor) { static const uint32_t rmask = 0x00ff0000; static const uint32_t gmask = 0x0000ff00; static const uint32_t bmask = 0x000000ff; - sdlSurface_ = SDL_CreateRGBSurfaceFrom((void*)(compositor_.GetCanvas().GetBuffer()), width, height, 32, - compositor_.GetCanvas().GetPitch(), rmask, gmask, bmask, 0); + const unsigned int width = compositor.GetCanvas().GetWidth(); + const unsigned int height = compositor.GetCanvas().GetHeight(); + + if (sdlSurface_ != NULL) + { + if (sdlSurface_->pixels == compositor.GetCanvas().GetBuffer() && + sdlSurface_->w == static_cast(width) && + sdlSurface_->h == static_cast(height) && + sdlSurface_->pitch == static_cast(compositor.GetCanvas().GetPitch())) + { + // The image from the compositor has not changed, no need to update the surface + return; + } + else + { + SDL_FreeSurface(sdlSurface_); + } + } + + sdlSurface_ = SDL_CreateRGBSurfaceFrom((void*)(compositor.GetCanvas().GetBuffer()), width, height, 32, + compositor.GetCanvas().GetPitch(), rmask, gmask, bmask, 0); if (!sdlSurface_) { LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface"; diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/SdlViewport.h --- a/Framework/Viewport/SdlViewport.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Viewport/SdlViewport.h Wed Apr 22 14:05:47 2020 +0200 @@ -39,26 +39,93 @@ #include "../OpenGL/SdlOpenGLContext.h" #include "../Scene2D/OpenGLCompositor.h" #include "../Scene2D/CairoCompositor.h" -#include "ViewportBase.h" +#include "IViewport.h" + +#include + +// TODO: required for UndoStack injection +// I don't like it either :) +#include + +#include namespace OrthancStone { - class SdlViewport : public ViewportBase + class UndoStack; + + class SdlViewport : public IViewport, + public boost::enable_shared_from_this { - public: - SdlViewport() + private: + boost::recursive_mutex mutex_; + uint32_t refreshEvent_; + boost::shared_ptr controller_; + std::unique_ptr compositor_; + + void SendRefreshEvent(); + + protected: + class SdlLock : public ILock { + private: + SdlViewport& that_; + boost::recursive_mutex::scoped_lock lock_; + + public: + SdlLock(SdlViewport& that) : + that_(that), + lock_(that.mutex_) + { + } + + virtual bool HasCompositor() const ORTHANC_OVERRIDE + { + return true; + } + + virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE; + + virtual ViewportController& GetController() ORTHANC_OVERRIDE + { + return *that_.controller_; + } + + virtual void Invalidate() ORTHANC_OVERRIDE + { + that_.SendRefreshEvent(); + } + }; + + void ClearCompositor() + { + compositor_.reset(); } - SdlViewport(boost::shared_ptr& scene) : - ViewportBase(scene) + void AcquireCompositor(ICompositor* compositor /* takes ownership */); + + protected: + SdlViewport(); + void PostConstructor(); + + public: + + bool IsRefreshEvent(const SDL_Event& event) const { + return (event.type == refreshEvent_); } - virtual SdlWindow& GetWindow() = 0; - + virtual ILock* Lock() ORTHANC_OVERRIDE + { + return new SdlLock(*this); + } + virtual void UpdateSize(unsigned int width, unsigned int height) = 0; + + virtual void ToggleMaximize() = 0; + + // Must be invoked from the main SDL thread + virtual void Paint() = 0; }; @@ -66,87 +133,57 @@ { private: SdlOpenGLContext context_; - std::unique_ptr compositor_; - - public: - SdlOpenGLViewport(const char* title, - unsigned int width, - unsigned int height, - bool allowDpiScaling = true); - - SdlOpenGLViewport(const char* title, - unsigned int width, - unsigned int height, - boost::shared_ptr& scene, - bool allowDpiScaling = true); - virtual SdlWindow& GetWindow() ORTHANC_OVERRIDE - { - return context_.GetWindow(); - } - - virtual void Refresh() ORTHANC_OVERRIDE; + private: + SdlOpenGLViewport(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); + public: + static boost::shared_ptr Create(const std::string&, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); - virtual void UpdateSize(unsigned int width, unsigned int height) ORTHANC_OVERRIDE - { - // nothing to do in OpenGL, the OpenGLCompositor::UpdateSize will be called automatically - } + + virtual ~SdlOpenGLViewport(); + + virtual void Paint() ORTHANC_OVERRIDE; - virtual bool HasCompositor() const ORTHANC_OVERRIDE - { - return true; - } + virtual void UpdateSize(unsigned int width, + unsigned int height) ORTHANC_OVERRIDE; - virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE - { - return *compositor_.get(); - } + virtual void ToggleMaximize() ORTHANC_OVERRIDE; }; class SdlCairoViewport : public SdlViewport { private: - SdlWindow window_; - CairoCompositor compositor_; - SDL_Surface* sdlSurface_; + SdlWindow window_; + SDL_Surface* sdlSurface_; + + void CreateSdlSurfaceFromCompositor(CairoCompositor& compositor); private: - void UpdateSdlSurfaceSize(unsigned int width, - unsigned int height); - + SdlCairoViewport(const char* title, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); public: - SdlCairoViewport(const char* title, + static boost::shared_ptr Create(const char* title, unsigned int width, unsigned int height, bool allowDpiScaling = true); - SdlCairoViewport(const char* title, - unsigned int width, - unsigned int height, - boost::shared_ptr& scene, - bool allowDpiScaling = true); - ~SdlCairoViewport(); + virtual ~SdlCairoViewport(); - virtual SdlWindow& GetWindow() ORTHANC_OVERRIDE - { - return window_; - } - - virtual void Refresh() ORTHANC_OVERRIDE; + virtual void Paint() ORTHANC_OVERRIDE; virtual void UpdateSize(unsigned int width, unsigned int height) ORTHANC_OVERRIDE; - virtual bool HasCompositor() const ORTHANC_OVERRIDE - { - return true; - } - - virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE - { - return compositor_; - } + virtual void ToggleMaximize() ORTHANC_OVERRIDE; }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/SdlWindow.cpp --- a/Framework/Viewport/SdlWindow.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Viewport/SdlWindow.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -31,6 +31,8 @@ #endif // WIN32 +#include +#include #include namespace OrthancStone @@ -154,7 +156,14 @@ void SdlWindow::Render(SDL_Surface* surface) { - //SDL_RenderClear(renderer_); + /** + * "You are strongly encouraged to call SDL_RenderClear() to + * initialize the backbuffer before starting each new frame's + * drawing, even if you plan to overwrite every pixel." + * https://wiki.libsdl.org/SDL_RenderPresent + **/ + SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255); + SDL_RenderClear(renderer_); // Clear the entire screen to our selected color SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer_, surface); if (texture != NULL) diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/SdlWindow.h --- a/Framework/Viewport/SdlWindow.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Viewport/SdlWindow.h Wed Apr 22 14:05:47 2020 +0200 @@ -23,18 +23,22 @@ #if ORTHANC_ENABLE_SDL == 1 -#include -#include #include +// Forward declaration of SDL type to avoid clashes with DCMTK headers +// on "typedef Sint8", in "StoneInitialization.cpp" +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Surface; + namespace OrthancStone { class SdlWindow : public boost::noncopyable { private: - SDL_Window *window_; - SDL_Renderer *renderer_; - bool maximized_; + struct SDL_Window *window_; + struct SDL_Renderer *renderer_; + bool maximized_; public: SdlWindow(const char* title, @@ -54,7 +58,12 @@ unsigned int GetHeight() const; - void Render(SDL_Surface* surface); + /** + * WARNING: "Refresh()" cannot only be called from the main SDL + * thread, in which the window was created. Otherwise, the + * renderer displays nothing! + **/ + void Render(struct SDL_Surface* surface); void ToggleMaximize(); diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/ViewportBase.cpp --- a/Framework/Viewport/ViewportBase.cpp Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "ViewportBase.h" - -#include - -#include - -namespace OrthancStone -{ - ViewportBase::ViewportBase() : - scene_(boost::make_shared()) - { - } - - - ViewportBase::ViewportBase(boost::shared_ptr& scene) : - scene_(scene) - { - if (scene.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - - ScenePoint2D ViewportBase::GetPixelCenterCoordinates(int x, int y) const - { - if (HasCompositor()) - { - const ICompositor& compositor = GetCompositor(); - return ScenePoint2D( - static_cast(x) + 0.5 - static_cast(compositor.GetCanvasWidth()) / 2.0, - static_cast(y) + 0.5 - static_cast(compositor.GetCanvasHeight()) / 2.0); - } - else - { - return ScenePoint2D(0, 0); - } - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/ViewportBase.h --- a/Framework/Viewport/ViewportBase.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#include "IViewport.h" - -#include - -namespace OrthancStone -{ - class ViewportBase : public IViewport - { - private: - boost::shared_ptr scene_; - - public: - ViewportBase(); - - ViewportBase(boost::shared_ptr& scene); - - virtual Scene2D& GetScene() ORTHANC_OVERRIDE - { - return *scene_; - } - - virtual ScenePoint2D GetPixelCenterCoordinates(int x, int y) const ORTHANC_OVERRIDE; - - virtual const ICompositor& GetCompositor() const ORTHANC_OVERRIDE - { - IViewport* mutableThis = - const_cast(static_cast(this)); - return mutableThis->GetCompositor(); - } - - }; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/WebAssemblyCairoViewport.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Viewport/WebAssemblyCairoViewport.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "WebAssemblyCairoViewport.h" + +#include "../Scene2D/CairoCompositor.h" + +#include + +namespace OrthancStone +{ + void WebAssemblyCairoViewport::GetCanvasSize(unsigned int& width, + unsigned int& height) + { + double w, h; + emscripten_get_element_css_size(GetCanvasCssSelector().c_str(), &w, &h); + + /** + * Emscripten has the function emscripten_get_element_css_size() + * to query the width and height of a named HTML element. I'm + * calling this first to get the initial size of the canvas DOM + * element, and then call emscripten_set_canvas_size() to + * initialize the framebuffer size of the canvas to the same + * size as its DOM element. + * https://floooh.github.io/2017/02/22/emsc-html.html + **/ + if (w > 0 && + h > 0) + { + width = static_cast(boost::math::iround(w)); + height = static_cast(boost::math::iround(h)); + } + else + { + width = 0; + height = 0; + } + } + + + void WebAssemblyCairoViewport::Paint(ICompositor& compositor, + ViewportController& controller) + { + compositor.Refresh(controller.GetScene()); + + // Create a temporary memory buffer for the canvas in JavaScript + Orthanc::ImageAccessor cairo; + dynamic_cast(compositor).GetCanvas().GetReadOnlyAccessor(cairo); + + const unsigned int width = cairo.GetWidth(); + const unsigned int height = cairo.GetHeight(); + + if (javascript_.get() == NULL || + javascript_->GetWidth() != width || + javascript_->GetHeight() != height) + { + javascript_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height, + true /* force minimal pitch */)); + } + + // Convert from BGRA32 memory layout (only color mode supported + // by Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32 + // (as expected by HTML5 canvas). This simply amounts to + // swapping the B and R channels. Alpha channel is also set to + // full opacity (255). + uint8_t* q = reinterpret_cast(javascript_->GetBuffer()); + for (unsigned int y = 0; y < height; y++) + { + const uint8_t* p = reinterpret_cast(cairo.GetConstRow(y)); + for (unsigned int x = 0; x < width; x++) + { + q[0] = p[2]; // R + q[1] = p[1]; // G + q[2] = p[0]; // B + q[3] = 255; // A + + p += 4; + q += 4; + } + } + + // Execute JavaScript commands to blit the image buffer onto the + // 2D drawing context of the HTML5 canvas + EM_ASM({ + const data = new Uint8ClampedArray(Module.HEAP8.buffer, $1, 4 * $2 * $3); + const img = new ImageData(data, $2, $3); + const ctx = document.getElementById(UTF8ToString($0)).getContext('2d'); + ctx.putImageData(img, 0, 0); + }, + GetCanvasId().c_str(), // $0 + javascript_->GetBuffer(), // $1 + javascript_->GetWidth(), // $2 + javascript_->GetHeight()); // $3 + } + + + void WebAssemblyCairoViewport::UpdateSize(ICompositor& compositor) + { + unsigned int width, height; + GetCanvasSize(width, height); + emscripten_set_canvas_element_size(GetCanvasCssSelector().c_str(), width, height); + + dynamic_cast(compositor).UpdateSize(width, height); + } + + + WebAssemblyCairoViewport::WebAssemblyCairoViewport( + const std::string& canvasId) : + WebAssemblyViewport(canvasId) + { + unsigned int width, height; + GetCanvasSize(width, height); + emscripten_set_canvas_element_size(GetCanvasCssSelector().c_str(), + width, + height); + + AcquireCompositor(new CairoCompositor(width, height)); + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/WebAssemblyCairoViewport.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Viewport/WebAssemblyCairoViewport.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,50 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "WebAssemblyViewport.h" + +namespace OrthancStone +{ + class WebAssemblyCairoViewport : public WebAssemblyViewport + { + private: + std::unique_ptr javascript_; + + void GetCanvasSize(unsigned int& width, + unsigned int& height); + + protected: + virtual void Paint(ICompositor& compositor, + ViewportController& controller) ORTHANC_OVERRIDE; + + virtual void UpdateSize(ICompositor& compositor) ORTHANC_OVERRIDE; + + public: + WebAssemblyCairoViewport(const std::string& canvasId); + + virtual ~WebAssemblyCairoViewport() + { + ClearCompositor(); + } + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/WebAssemblyViewport.cpp --- a/Framework/Viewport/WebAssemblyViewport.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Viewport/WebAssemblyViewport.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,311 +21,314 @@ #include "WebAssemblyViewport.h" -#include "../StoneException.h" +#include "../Toolbox/GenericToolbox.h" -#include +#include + +#include +#include namespace OrthancStone { - WebAssemblyOpenGLViewport::WebAssemblyOpenGLViewport(const std::string& canvas) - : WebAssemblyViewport(canvas) - , context_(canvas) - , cssWidth_(0) // will be set in Refresh() - , cssHeight_(0) // ditto - , pixelWidth_(0) // ditto - , pixelHeight_(0) // ditto + static void ConvertMouseEvent(PointerEvent& target, + const EmscriptenMouseEvent& source, + const ICompositor& compositor) { - compositor_.reset(new OpenGLCompositor(context_, GetScene())); - RegisterContextCallbacks(); - } + int x = static_cast(source.targetX); + int y = static_cast(source.targetY); - WebAssemblyOpenGLViewport::WebAssemblyOpenGLViewport(const std::string& canvas, - boost::shared_ptr& scene) - : WebAssemblyViewport(canvas, scene) - , context_(canvas) - , cssWidth_(0) // will be set in Refresh() - , cssHeight_(0) // ditto - , pixelWidth_(0) // ditto - , pixelHeight_(0) // ditto - { - compositor_.reset(new OpenGLCompositor(context_, GetScene())); - RegisterContextCallbacks(); - } + switch (source.button) + { + case 0: + target.SetMouseButton(MouseButton_Left); + break; - void WebAssemblyOpenGLViewport::UpdateSize() - { - context_.UpdateSize(); // First read the size of the canvas + case 1: + target.SetMouseButton(MouseButton_Middle); + break; - if (compositor_.get() != NULL) - { - compositor_->Refresh(); // Then refresh the content of the canvas + case 2: + target.SetMouseButton(MouseButton_Right); + break; + + default: + target.SetMouseButton(MouseButton_None); + break; } - } - - /* - typedef EM_BOOL (*em_webgl_context_callback)(int eventType, const void *reserved, void *userData); - - EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED - - EMSCRIPTEN_RESULT emscripten_set_webglcontextlost_callback( - const char *target, void *userData, EM_BOOL useCapture, em_webgl_context_callback callback) - - EMSCRIPTEN_RESULT emscripten_set_webglcontextrestored_callback( - const char *target, void *userData, EM_BOOL useCapture, em_webgl_context_callback callback) - - */ - - EM_BOOL WebAssemblyOpenGLViewport_OpenGLContextLost_callback( - int eventType, const void* reserved, void* userData) - { - ORTHANC_ASSERT(eventType == EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST); - WebAssemblyOpenGLViewport* viewport = reinterpret_cast(userData); - return viewport->OpenGLContextLost(); - } - - EM_BOOL WebAssemblyOpenGLViewport_OpenGLContextRestored_callback( - int eventType, const void* reserved, void* userData) - { - ORTHANC_ASSERT(eventType == EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED); - WebAssemblyOpenGLViewport* viewport = reinterpret_cast(userData); - return viewport->OpenGLContextRestored(); - } - - void WebAssemblyOpenGLViewport::DisableCompositor() - { - compositor_.reset(); + + target.AddPosition(compositor.GetPixelCenterCoordinates(x, y)); + target.SetAltModifier(source.altKey); + target.SetControlModifier(source.ctrlKey); + target.SetShiftModifier(source.shiftKey); } - ICompositor& WebAssemblyOpenGLViewport::GetCompositor() - { - if (compositor_.get() == NULL) - { - // "HasCompositor()" should have been called - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - return *compositor_; - } - } - void WebAssemblyOpenGLViewport::UpdateSizeIfNeeded() + class WebAssemblyViewport::WasmLock : public ILock { - bool needsRefresh = false; - std::string canvasId = GetCanvasIdentifier(); - - { - double cssWidth = 0; - double cssHeight = 0; - EMSCRIPTEN_RESULT res = EMSCRIPTEN_RESULT_SUCCESS; - res = - emscripten_get_element_css_size(canvasId.c_str(), &cssWidth, &cssHeight); + private: + WebAssemblyViewport& that_; - if (res == EMSCRIPTEN_RESULT_SUCCESS) - { - if ((cssWidth != cssWidth_) || (cssHeight != cssHeight_)) - { - cssWidth_ = cssWidth; - cssHeight_ = cssHeight; - needsRefresh = true; - } - } + public: + WasmLock(WebAssemblyViewport& that) : + that_(that) + { } + virtual bool HasCompositor() const ORTHANC_OVERRIDE { - int pixelWidth = 0; - int pixelHeight = 0; - EMSCRIPTEN_RESULT res = EMSCRIPTEN_RESULT_SUCCESS; - res = - emscripten_get_canvas_element_size(canvasId.c_str(), &pixelWidth, &pixelHeight); - - if (res == EMSCRIPTEN_RESULT_SUCCESS) - { - if ((pixelWidth != pixelWidth_) || (pixelHeight != pixelHeight_)) - { - pixelWidth_ = pixelWidth; - pixelHeight_ = pixelHeight; - needsRefresh = true; - } - } + return that_.compositor_.get() != NULL; } - if (needsRefresh) - UpdateSize(); - } - - void WebAssemblyOpenGLViewport::Refresh() - { - try + virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE { - // first, we check if the canvas size (both css size in css pixels and - // backing store) have changed. if so, we call UpdateSize to deal with - // it - - LOG(TRACE) << "WebAssemblyOpenGLViewport::Refresh"; - - // maybe the canvas size has changed and we need to update the - // canvas backing store size - UpdateSizeIfNeeded(); - - if (HasCompositor()) + if (that_.compositor_.get() == NULL) { - GetCompositor().Refresh(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } else { - // this block was added because of (perceived?) bugs in the - // browser where the WebGL contexts are NOT automatically restored - // after being lost. - // the WebGL context has been lost. Sce - - //LOG(ERROR) << "About to call WebAssemblyOpenGLContext::TryRecreate()."; - //LOG(ERROR) << "Before calling it, isContextLost == " << context_.IsContextLost(); - - if (!context_.IsContextLost()) - { - LOG(TRACE) << "Context restored!"; - //LOG(ERROR) << "After calling it, isContextLost == " << context_.IsContextLost(); - RestoreCompositor(); - UpdateSize(); - } + return *that_.compositor_; } } - catch (const StoneException& e) + + virtual ViewportController& GetController() ORTHANC_OVERRIDE + { + assert(that_.controller_); + return *that_.controller_; + } + + virtual void Invalidate() ORTHANC_OVERRIDE + { + that_.Invalidate(); + } + }; + + + EM_BOOL WebAssemblyViewport::OnRequestAnimationFrame(double time, void *userData) + { + LOG(TRACE) << __func__; + WebAssemblyViewport* that = reinterpret_cast(userData); + + if (that->compositor_.get() != NULL && + that->controller_ /* should always be true */) + { + that->Paint(*that->compositor_, *that->controller_); + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + EM_BOOL WebAssemblyViewport::OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) + { + LOG(TRACE) << __func__; + WebAssemblyViewport* that = reinterpret_cast(userData); + + if (that->compositor_.get() != NULL) { - if (e.GetErrorCode() == ErrorCode_WebGLContextLost) + that->UpdateSize(*that->compositor_); + that->Invalidate(); + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + + EM_BOOL WebAssemblyViewport::OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) + { + WebAssemblyViewport* that = reinterpret_cast(userData); + + LOG(TRACE) << "mouse down: " << that->GetCanvasCssSelector(); + + if (that->compositor_.get() != NULL && + that->interactor_.get() != NULL) + { + PointerEvent pointer; + ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); + + that->controller_->HandleMousePress(*that->interactor_, pointer, + that->compositor_->GetCanvasWidth(), + that->compositor_->GetCanvasHeight()); + that->Invalidate(); + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + + EM_BOOL WebAssemblyViewport::OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) + { + WebAssemblyViewport* that = reinterpret_cast(userData); + + if (that->compositor_.get() != NULL && + that->controller_->HasActiveTracker()) + { + PointerEvent pointer; + ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); + if (that->controller_->HandleMouseMove(pointer)) { - LOG(WARNING) << "Context is lost! Compositor will be disabled."; - DisableCompositor(); - // we now need to wait for the "context restored" callback - } - else - { - throw; + that->Invalidate(); } } - catch (...) - { - // something else nasty happened - throw; - } + + LOG(TRACE) << "Exiting: " << __func__; + return true; } - - void WebAssemblyOpenGLViewport::RestoreCompositor() + + EM_BOOL WebAssemblyViewport::OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { - // the context must have been restored! - ORTHANC_ASSERT(!context_.IsContextLost()); - if (compositor_.get() == NULL) + LOG(TRACE) << __func__; + WebAssemblyViewport* that = reinterpret_cast(userData); + + if (that->compositor_.get() != NULL) { - compositor_.reset(new OpenGLCompositor(context_, GetScene())); - } - else - { - LOG(WARNING) << "RestoreCompositor() called for \"" << GetCanvasIdentifier() << "\" while it was NOT lost! Nothing done."; + PointerEvent pointer; + ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); + that->controller_->HandleMouseRelease(pointer); + that->Invalidate(); } - } - bool WebAssemblyOpenGLViewport::OpenGLContextLost() - { - LOG(ERROR) << "WebAssemblyOpenGLViewport::OpenGLContextLost() for canvas: " << GetCanvasIdentifier(); - DisableCompositor(); + LOG(TRACE) << "Exiting: " << __func__; return true; } - bool WebAssemblyOpenGLViewport::OpenGLContextRestored() + void WebAssemblyViewport::Invalidate() + { + emscripten_request_animation_frame(OnRequestAnimationFrame, reinterpret_cast(this)); + } + + void WebAssemblyViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */) { - LOG(ERROR) << "WebAssemblyOpenGLViewport::OpenGLContextRestored() for canvas: " << GetCanvasIdentifier(); - - // maybe the context has already been restored by other means (the - // Refresh() function) - if (!HasCompositor()) + if (compositor == NULL) { - RestoreCompositor(); - UpdateSize(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } - return false; + else + { + compositor_.reset(compositor); + } } - void WebAssemblyOpenGLViewport::RegisterContextCallbacks() - { -#if 0 - // DISABLED ON 2019-08-20 and replaced by external JS calls because I could - // not get emscripten API to work - // TODO: what's the impact of userCapture=true ? - const char* canvasId = GetCanvasIdentifier().c_str(); - void* that = reinterpret_cast(this); - EMSCRIPTEN_RESULT status = EMSCRIPTEN_RESULT_SUCCESS; +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 +// everything OK..... we're using the new setting +#else +#pragma message("WARNING: DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR is not defined or equal to 0. Stone will use the OLD Emscripten rules for DOM element selection.") +#endif - //status = emscripten_set_webglcontextlost_callback(canvasId, that, true, WebAssemblyOpenGLViewport_OpenGLContextLost_callback); - //if (status != EMSCRIPTEN_RESULT_SUCCESS) - //{ - // std::stringstream ss; - // ss << "Error while calling emscripten_set_webglcontextlost_callback for: \"" << GetCanvasIdentifier() << "\""; - // std::string msg = ss.str(); - // LOG(ERROR) << msg; - // ORTHANC_ASSERT(false, msg.c_str()); - //} - - status = emscripten_set_webglcontextrestored_callback(canvasId, that, true, WebAssemblyOpenGLViewport_OpenGLContextRestored_callback); - if (status != EMSCRIPTEN_RESULT_SUCCESS) - { - std::stringstream ss; - ss << "Error while calling emscripten_set_webglcontextrestored_callback for: \"" << GetCanvasIdentifier() << "\""; - std::string msg = ss.str(); - LOG(ERROR) << msg; - ORTHANC_ASSERT(false, msg.c_str()); - } - LOG(TRACE) << "WebAssemblyOpenGLViewport::RegisterContextCallbacks() SUCCESS!!!"; + WebAssemblyViewport::WebAssemblyViewport( + const std::string& canvasId, bool enableEmscriptenMouseEvents) : + canvasId_(canvasId), +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 + canvasCssSelector_("#" + canvasId), +#else + canvasCssSelector_(canvasId), #endif - } - - WebAssemblyCairoViewport::WebAssemblyCairoViewport(const std::string& canvas) : - WebAssemblyViewport(canvas), - canvas_(canvas), - compositor_(GetScene(), 1024, 768) + interactor_(new DefaultViewportInteractor), + enableEmscriptenMouseEvents_(enableEmscriptenMouseEvents) { } - WebAssemblyCairoViewport::WebAssemblyCairoViewport(const std::string& canvas, - boost::shared_ptr& scene) : - WebAssemblyViewport(canvas, scene), - canvas_(canvas), - compositor_(GetScene(), 1024, 768) + void WebAssemblyViewport::PostConstructor() { + boost::shared_ptr viewport = shared_from_this(); + controller_.reset(new ViewportController(viewport)); + + LOG(INFO) << "Initializing Stone viewport on HTML canvas: " + << canvasId_; + + if (canvasId_.empty() || + canvasId_[0] == '#') + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "The canvas identifier must not start with '#'"); + } + + // Disable right-click on the canvas (i.e. context menu) + EM_ASM({ + document.getElementById(UTF8ToString($0)).oncontextmenu = + function(event) + { + event.preventDefault(); + } + }, + canvasId_.c_str() // $0 + ); + + // It is not possible to monitor the resizing of individual + // canvas, so we track the full window of the browser + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, + reinterpret_cast(this), + false, + OnResize); + + if (enableEmscriptenMouseEvents_) + { + + // if any of this function causes an error in the console, please + // make sure you are using the new (as of 1.39.x) version of + // emscripten element lookup rules( pass + // "-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1" to the linker. + + emscripten_set_mousedown_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + OnMouseDown); + + emscripten_set_mousemove_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + OnMouseMove); + + emscripten_set_mouseup_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + OnMouseUp); + } } - void WebAssemblyCairoViewport::UpdateSize() + WebAssemblyViewport::~WebAssemblyViewport() { - LOG(INFO) << "updating cairo viewport size"; - double w, h; - emscripten_get_element_css_size(canvas_.c_str(), &w, &h); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, + reinterpret_cast(this), + false, + NULL); + + if (enableEmscriptenMouseEvents_) + { + + emscripten_set_mousedown_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + OnMouseDown); + + emscripten_set_mousemove_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + OnMouseMove); - /** - * Emscripten has the function emscripten_get_element_css_size() - * to query the width and height of a named HTML element. I'm - * calling this first to get the initial size of the canvas DOM - * element, and then call emscripten_set_canvas_size() to - * initialize the framebuffer size of the canvas to the same - * size as its DOM element. - * https://floooh.github.io/2017/02/22/emsc-html.html - **/ - unsigned int canvasWidth = 0; - unsigned int canvasHeight = 0; - - if (w > 0 || - h > 0) + emscripten_set_mouseup_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + OnMouseUp); + } + } + + IViewport::ILock* WebAssemblyViewport::Lock() + { + return new WasmLock(*this); + } + + void WebAssemblyViewport::AcquireInteractor(IViewportInteractor* interactor) + { + if (interactor == NULL) { - canvasWidth = static_cast(boost::math::iround(w)); - canvasHeight = static_cast(boost::math::iround(h)); + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } - - emscripten_set_canvas_element_size(canvas_.c_str(), canvasWidth, canvasHeight); - compositor_.UpdateSize(canvasWidth, canvasHeight); - } - - void WebAssemblyCairoViewport::Refresh() - { - LOG(INFO) << "refreshing cairo viewport, TODO: blit to the canvans.getContext('2d')"; - GetCompositor().Refresh(); + else + { + interactor_.reset(interactor); + } } } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/WebAssemblyViewport.h --- a/Framework/Viewport/WebAssemblyViewport.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Viewport/WebAssemblyViewport.h Wed Apr 22 14:05:47 2020 +0200 @@ -13,7 +13,7 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **/ @@ -21,113 +21,102 @@ #pragma once -#include "../OpenGL/WebAssemblyOpenGLContext.h" -#include "../Scene2D/OpenGLCompositor.h" -#include "../Scene2D/CairoCompositor.h" -#include "ViewportBase.h" +#if !defined(ORTHANC_ENABLE_WASM) +# error Macro ORTHANC_ENABLE_WASM must be defined +#endif + +#if ORTHANC_ENABLE_WASM != 1 +# error This file can only be used if targeting WebAssembly +#endif + +#include "IViewport.h" + +#include + +#include +#include + +#include +#include namespace OrthancStone { - class WebAssemblyViewport : public ViewportBase + class WebAssemblyViewport : public IViewport, + public boost::enable_shared_from_this + { private: - std::string canvasIdentifier_; + class WasmLock; + + std::string canvasId_; + std::string canvasCssSelector_; + std::unique_ptr compositor_; + std::unique_ptr controller_; + std::unique_ptr interactor_; + bool enableEmscriptenMouseEvents_; - public: - WebAssemblyViewport(const std::string& canvasIdentifier) - : canvasIdentifier_(canvasIdentifier) + static EM_BOOL OnRequestAnimationFrame(double time, void *userData); + + static EM_BOOL OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData); + + static EM_BOOL OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + + static EM_BOOL OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + + static EM_BOOL OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + + protected: + void Invalidate(); + + void ClearCompositor() { - } - - WebAssemblyViewport(const std::string& canvasIdentifier, - boost::shared_ptr& scene) - : ViewportBase(scene) - , canvasIdentifier_(canvasIdentifier) - { + compositor_.reset(); } - const std::string& GetCanvasIdentifier() const + bool HasCompositor() const { - return canvasIdentifier_; + return compositor_.get() != NULL; } - }; + + void AcquireCompositor(ICompositor* compositor /* takes ownership */); + virtual void Paint(ICompositor& compositor, + ViewportController& controller) = 0; - class WebAssemblyOpenGLViewport : public WebAssemblyViewport - { - private: - OpenGL::WebAssemblyOpenGLContext context_; - std::unique_ptr compositor_; - double cssWidth_; - double cssHeight_; - int pixelWidth_; - int pixelHeight_; + virtual void UpdateSize(ICompositor& compositor) = 0; - private: - void UpdateSizeIfNeeded(); + /** + The second argument is temporary and should be deleted once the migration + to interactors is finished. + */ + WebAssemblyViewport(const std::string& canvasId, + bool enableEmscriptenMouseEvents = true); + + void PostConstructor(); public: - WebAssemblyOpenGLViewport(const std::string& canvas); - - WebAssemblyOpenGLViewport(const std::string& canvas, - boost::shared_ptr& scene); - - // This function must be called each time the browser window is resized - void UpdateSize(); + virtual ILock* Lock() ORTHANC_OVERRIDE; + + ~WebAssemblyViewport(); + - virtual bool HasCompositor() const ORTHANC_OVERRIDE + /** + This method takes ownership + */ + void AcquireInteractor(IViewportInteractor* interactor); + + const std::string& GetCanvasId() const { - return (compositor_.get() != NULL); - } - - bool IsContextLost() - { - return context_.IsContextLost(); + return canvasId_; } - virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE; - - virtual void Refresh() ORTHANC_OVERRIDE; - - // this does NOT return whether the context is lost! This is called to - // tell Stone that the context has been lost - bool OpenGLContextLost(); - - // This should be called to indicate that the context has been lost - bool OpenGLContextRestored(); - - private: - void DisableCompositor(); - void RestoreCompositor(); - - void RegisterContextCallbacks(); - }; - - - class WebAssemblyCairoViewport : public WebAssemblyViewport - { - private: - CairoCompositor compositor_; - std::string canvas_; - - public: - WebAssemblyCairoViewport(const std::string& canvas); - - WebAssemblyCairoViewport(const std::string& canvas, - boost::shared_ptr& scene); - - void UpdateSize(); - - virtual void Refresh() ORTHANC_OVERRIDE; - - virtual bool HasCompositor() const ORTHANC_OVERRIDE + /** + emscripten functions requires the css selector for the canvas. This is + different from the canvas id (the syntax is '#mycanvasid') + */ + const std::string& GetCanvasCssSelector() const { - return true; - } - - virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE - { - return compositor_; + return canvasCssSelector_; } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/WebGLViewport.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Viewport/WebGLViewport.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,105 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "WebGLViewport.h" + +#include "../StoneException.h" +#include "../Scene2D/OpenGLCompositor.h" + +namespace OrthancStone +{ + void WebGLViewport::Paint(ICompositor& compositor, + ViewportController& controller) + { + try + { + compositor.Refresh(controller.GetScene()); + + /** + * No need to manually swap the buffer: "Rendered WebGL content + * is implicitly presented (displayed to the user) on the canvas + * when the event handler that renders with WebGL returns back + * to the browser event loop." + * https://emscripten.org/docs/api_reference/html5.h.html#webgl-context + * + * Could call "emscripten_webgl_commit_frame()" if + * "explicitSwapControl" option were set to "true". + **/ + } + catch (const StoneException& e) + { + // Ignore problems about the loss of the WebGL context (edge case) + if (e.GetErrorCode() == ErrorCode_WebGLContextLost) + { + return; + } + else + { + throw; + } + } + } + + + void WebGLViewport::UpdateSize(ICompositor& compositor) + { + try + { + context_.UpdateSize(); + } + catch (const StoneException& e) + { + // Ignore problems about the loss of the WebGL context (edge case) + if (e.GetErrorCode() == ErrorCode_WebGLContextLost) + { + return; + } + else + { + throw; + } + } + } + + WebGLViewport::WebGLViewport(const std::string& canvasId, bool enableEmscriptenMouseEvents) : + WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents), + context_(GetCanvasCssSelector()) + { + AcquireCompositor(new OpenGLCompositor(context_)); + } + + boost::shared_ptr WebGLViewport::Create( + const std::string& canvasId, bool enableEmscriptenMouseEvents) + { + boost::shared_ptr that = boost::shared_ptr( + new WebGLViewport(canvasId, enableEmscriptenMouseEvents)); + + that->WebAssemblyViewport::PostConstructor(); + return that; + } + + WebGLViewport::~WebGLViewport() + { + // Make sure to delete the compositor before its parent "context_" gets + // deleted + ClearCompositor(); + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/WebGLViewport.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Viewport/WebGLViewport.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "WebAssemblyViewport.h" +#include "../OpenGL/WebAssemblyOpenGLContext.h" + +namespace OrthancStone +{ + class WebGLViewport : public WebAssemblyViewport + { + private: + OpenGL::WebAssemblyOpenGLContext context_; + + WebGLViewport(const std::string& canvasId, bool enableEmscriptenMouseEvents); + + protected: + virtual void Paint(ICompositor& compositor, + ViewportController& controller) ORTHANC_OVERRIDE; + + virtual void UpdateSize(ICompositor& compositor) ORTHANC_OVERRIDE; + + public: + static boost::shared_ptr Create(const std::string& canvasId, bool enableEmscriptenMouseEvents = true); + + virtual ~WebGLViewport(); + + bool IsContextLost() + { + return context_.IsContextLost(); + } + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/WebGLViewportsRegistry.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Viewport/WebGLViewportsRegistry.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,185 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "WebGLViewportsRegistry.h" + +#include "../Toolbox/GenericToolbox.h" + +#include + +#include + +namespace OrthancStone +{ + void WebGLViewportsRegistry::LaunchTimer() + { + timeOutID_ = emscripten_set_timeout( + OnTimeoutCallback, + timeoutMS_, + reinterpret_cast(this)); + } + + void WebGLViewportsRegistry::OnTimeout() + { + for (Viewports::iterator it = viewports_.begin(); + it != viewports_.end(); + ++it) + { + if (it->second == NULL || + it->second->IsContextLost()) + { + LOG(INFO) << "WebGL context lost for canvas: " << it->first; + + // Try and duplicate the HTML5 canvas in the DOM + EM_ASM({ + var canvas = document.getElementById(UTF8ToString($0)); + if (canvas) { + var parent = canvas.parentElement; + if (parent) { + var cloned = canvas.cloneNode(true /* deep copy */); + parent.insertBefore(cloned, canvas); + parent.removeChild(canvas); + } + } + }, + it->first.c_str() // $0 = ID of the canvas + ); + + // At this point, the old canvas is removed from the DOM and + // replaced by a fresh one with the same ID: Recreate the + // WebGL context on the new canvas + boost::shared_ptr viewport; + + // we need to steal the properties from the old viewport + // and set them to the new viewport + { + std::unique_ptr lock(it->second->Lock()); + + // TODO: remove ViewportController + Scene2D* scene = lock->GetController().ReleaseScene(); + viewport = WebGLViewport::Create(it->first); + + { + std::unique_ptr newLock(viewport->Lock()); + newLock->GetController().AcquireScene(scene); + } + } + + // Replace the old WebGL viewport by the new one + it->second = viewport; + + // Tag the fresh canvas as needing a repaint + { + std::unique_ptr lock(it->second->Lock()); + lock->Invalidate(); + } + } + } + + LaunchTimer(); + } + + void WebGLViewportsRegistry::OnTimeoutCallback(void *userData) + { + // This object dies with the process or tab. + WebGLViewportsRegistry* that = + reinterpret_cast(userData); + that->OnTimeout(); + } + + WebGLViewportsRegistry::WebGLViewportsRegistry(double timeoutMS) : + timeoutMS_(timeoutMS), + timeOutID_(0) + { + if (timeoutMS <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + LaunchTimer(); + } + + WebGLViewportsRegistry::~WebGLViewportsRegistry() + { + emscripten_clear_timeout(timeOutID_); + Clear(); + } + + boost::shared_ptr WebGLViewportsRegistry::Add( + const std::string& canvasId) + { + if (viewports_.find(canvasId) != viewports_.end()) + { + LOG(ERROR) << "Canvas was already registered: " << canvasId; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + boost::shared_ptr viewport = + WebGLViewport::Create(canvasId); + viewports_[canvasId] = viewport; + return viewport; + } + } + + void WebGLViewportsRegistry::Remove(const std::string& canvasId) + { + Viewports::iterator found = viewports_.find(canvasId); + + if (found == viewports_.end()) + { + LOG(ERROR) << "Cannot remove unregistered canvas: " << canvasId; + } + else + { + viewports_.erase(found); + } + } + + void WebGLViewportsRegistry::Clear() + { + viewports_.clear(); + } + + WebGLViewportsRegistry::Accessor::Accessor(WebGLViewportsRegistry& that, + const std::string& canvasId) : + that_(that) + { + Viewports::iterator viewport = that.viewports_.find(canvasId); + if (viewport != that.viewports_.end() && + viewport->second != NULL) + { + lock_.reset(viewport->second->Lock()); + } + } + + IViewport::ILock& WebGLViewportsRegistry::Accessor::GetViewport() const + { + if (IsValid()) + { + return *lock_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Viewport/WebGLViewportsRegistry.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Viewport/WebGLViewportsRegistry.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,83 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "WebGLViewport.h" + +#include + +namespace OrthancStone +{ + /** + * This singleton class must be used if many WebGL viewports are + * created by the higher-level application, implying possible loss + * of WebGL contexts. The object will run an infinite update loop + * that checks whether all the WebGL context are still valid (not + * lost). If some WebGL context is lost, it is automatically + * reinitialized by created a fresh HTML5 canvas. + **/ + class WebGLViewportsRegistry : public boost::noncopyable, + public boost::enable_shared_from_this + { + private: + typedef std::map > Viewports; + + double timeoutMS_; + Viewports viewports_; + long timeOutID_; + + void LaunchTimer(); + + void OnTimeout(); + + static void OnTimeoutCallback(void *userData); + + public: + WebGLViewportsRegistry(double timeoutMS /* in milliseconds */); + + ~WebGLViewportsRegistry(); + + boost::shared_ptr Add(const std::string& canvasId); + + void Remove(const std::string& canvasId); + + void Clear(); + + class Accessor : public boost::noncopyable + { + private: + WebGLViewportsRegistry& that_; + std::unique_ptr lock_; + + public: + Accessor(WebGLViewportsRegistry& that, + const std::string& canvasId); + + bool IsValid() const + { + return lock_.get() != NULL; + } + + IViewport::ILock& GetViewport() const; + }; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Volumes/DicomVolumeImage.h --- a/Framework/Volumes/DicomVolumeImage.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Volumes/DicomVolumeImage.h Wed Apr 22 14:05:47 2020 +0200 @@ -28,14 +28,6 @@ namespace OrthancStone { - class IGeometryProvider - { - public: - virtual ~IGeometryProvider() {} - virtual bool HasGeometry() const = 0; - virtual const VolumeImageGeometry& GetImageGeometry() const = 0; - }; - /** This class combines a 3D image buffer, a 3D volume geometry and information about the DICOM parameters of the series. @@ -44,6 +36,7 @@ class DicomVolumeImage : public boost::noncopyable { public: + // TODO - Are these messages still useful? ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, DicomVolumeImage); ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentUpdatedMessage, DicomVolumeImage); @@ -67,8 +60,10 @@ } void Initialize(const VolumeImageGeometry& geometry, - Orthanc::PixelFormat format, bool computeRange = false); + Orthanc::PixelFormat format, + bool computeRange = false); + // Used by volume slicers void SetDicomParameters(const DicomInstanceParameters& parameters); uint64_t GetRevision() const diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Volumes/DicomVolumeImageMPRSlicer.cpp --- a/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -47,8 +47,7 @@ revision_(volume_.GetRevision()) { valid_ = (volume_.HasDicomParameters() && - volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, - cuttingPlane)); + volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane)); } @@ -85,21 +84,6 @@ texture.reset(dynamic_cast (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters))); - - // -#if 0 - Orthanc::JpegWriter writer; - writer.SetQuality(60); - static int index = 0; - std::string filePath = "C:\\temp\\sliceReader_P"; - filePath += boost::lexical_cast(projection_); - filePath += "_I"; - filePath += boost::lexical_cast(index); - filePath += ".jpg"; - index++; - writer.WriteToFile(filePath, reader.GetAccessor()); -#endif - // } const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_); @@ -108,53 +92,11 @@ cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX()); - // -#if 0 { - LOG(ERROR) << "+----------------------------------------------------+"; - LOG(ERROR) << "| DicomVolumeImageMPRSlicer::Slice::CreateSceneLayer |"; - LOG(ERROR) << "+----------------------------------------------------+"; - std::string projectionString; - switch (projection_) - { - case VolumeProjection_Coronal: - projectionString = "CORONAL"; - break; - case VolumeProjection_Axial: - projectionString = "CORONAL"; - break; - case VolumeProjection_Sagittal: - projectionString = "SAGITTAL"; - break; - default: - ORTHANC_ASSERT(false); - } - if(volume_.GetGeometry().GetDepth() == 200) - LOG(ERROR) << "| CT IMAGE 512x512 with projection " << projectionString; - else - LOG(ERROR) << "| RTDOSE IMAGE NNNxNNN with projection " << projectionString; - LOG(ERROR) << "+----------------------------------------------------+"; - LOG(ERROR) << "| cuttingPlane = " << cuttingPlane; - LOG(ERROR) << "| point to project = " << system.GetOrigin(); - LOG(ERROR) << "| result = x0: " << x0 << " y0: " << y0; - LOG(ERROR) << "+----------------------- END ------------------------+"; + double xz, yz; + cuttingPlane.ProjectPoint(xz, yz, LinearAlgebra::CreateVector(0, 0, 0)); + texture->SetOrigin(x0 - xz, y0 - yz); } -#endif - // - -#if 1 // BGO 2019-08-13 - // The sagittal coordinate system has a Y vector going down. The displayed - // image (scene coords) has a Y vector pointing upwards (towards the patient - // coord Z index) - // we need to flip the Y local coordinates to get the scene-coord offset. - // TODO: this is quite ugly. Isn't there a better way? - if(projection_ == VolumeProjection_Sagittal) - texture->SetOrigin(x0, -y0); - else - texture->SetOrigin(x0, y0); -#else - texture->SetOrigin(x0, y0); -#endif double dx = x1 - x0; double dy = y1 - y0; @@ -167,19 +109,6 @@ Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_); texture->SetPixelSpacing(tmp[0], tmp[1]); - // - { - //using std::endl; - //std::stringstream ss; - //ss << "DicomVolumeImageMPRSlicer::Slice::CreateSceneLayer | cuttingPlane = " << cuttingPlane << " | projection_ = " << projection_ << endl; - //ss << "volume_.GetGeometry().GetProjectionGeometry(projection_) = " << system << endl; - //ss << "cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); --> | x0 = " << x0 << " | y0 = " << y0 << "| x1 = " << x1 << " | y1 = " << y1 << endl; - //ss << "volume_.GetGeometry() = " << volume_.GetGeometry() << endl; - //ss << "volume_.GetGeometry() = " << volume_.GetGeometry() << endl; - //LOG(ERROR) << ss.str(); - } - // - return texture.release(); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Volumes/IGeometryProvider.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Volumes/IGeometryProvider.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,35 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Volumes/VolumeImageGeometry.h" + +namespace OrthancStone +{ + class IGeometryProvider + { + public: + virtual ~IGeometryProvider() {} + virtual bool HasGeometry() const = 0; + virtual const OrthancStone::VolumeImageGeometry& GetImageGeometry() const = 0; + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Volumes/IVolumeSlicer.cpp~ --- a/Framework/Volumes/IVolumeSlicer.cpp~ Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -namespace OrthancStone -{ - /** - 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; - }; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Volumes/IVolumeSlicer.h~ --- a/Framework/Volumes/IVolumeSlicer.h~ Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -namespace OrthancStone -{ - /** - 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; - }; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Volumes/VolumeImageGeometry.cpp --- a/Framework/Volumes/VolumeImageGeometry.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Volumes/VolumeImageGeometry.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -39,7 +39,7 @@ sagittalGeometry_ = CoordinateSystem3D(p, axialGeometry_.GetAxisY(), - axialGeometry_.GetNormal()); + -axialGeometry_.GetNormal()); Vector origin = ( axialGeometry_.MapSliceToWorldCoordinates(-0.5 * voxelDimensions_[0], @@ -296,16 +296,18 @@ { return false; } - - unsigned int d = static_cast(std::floor(z)); - if (d >= projectionDepth) - { - return false; - } else { - slice = d; - return true; + unsigned int d = static_cast(std::floor(z)); + if (d >= projectionDepth) + { + return false; + } + else + { + slice = d; + return true; + } } } @@ -321,7 +323,18 @@ Vector dim = GetVoxelDimensions(projection); CoordinateSystem3D plane = GetProjectionGeometry(projection); - plane.SetOrigin(plane.GetOrigin() + static_cast(z) * plane.GetNormal() * dim[2]); + Vector normal = plane.GetNormal(); + if (projection == VolumeProjection_Sagittal) + { + /** + * WARNING: In sagittal geometry, the normal points to REDUCING + * X-axis in the 3D world. This is necessary to keep the + * right-hand coordinate system. Hence the negation. + **/ + normal = -normal; + } + + plane.SetOrigin(plane.GetOrigin() + static_cast(z) * dim[2] * normal); return plane; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Volumes/VolumeSceneLayerSource.cpp --- a/Framework/Volumes/VolumeSceneLayerSource.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Volumes/VolumeSceneLayerSource.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,6 +21,9 @@ #include "VolumeSceneLayerSource.h" +#include "../Scene2D/NullLayer.h" +#include "../StoneException.h" + #include namespace OrthancStone @@ -42,17 +45,23 @@ } - VolumeSceneLayerSource::VolumeSceneLayerSource(Scene2D& scene, - int layerDepth, - const boost::shared_ptr& slicer) : - scene_(scene), - layerDepth_(layerDepth), - slicer_(slicer) + VolumeSceneLayerSource::VolumeSceneLayerSource( + Scene2D& scene, + int layerDepth, + const boost::shared_ptr& slicer) + : scene_(scene) + , layerDepth_(layerDepth) + , slicer_(slicer) { if (slicer == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } + ORTHANC_ASSERT(!scene_.HasLayer(layerDepth_)); + + // we need to book the scene layer depth by adding a dummy layer + std::unique_ptr nullLayer(new NullLayer); + scene_.SetLayer(layerDepth_,nullLayer.release()); } VolumeSceneLayerSource::~VolumeSceneLayerSource() diff -r 4431ffdcc2a4 -r f4a06ad1580b Framework/Volumes/VolumeSceneLayerSource.h --- a/Framework/Volumes/VolumeSceneLayerSource.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Framework/Volumes/VolumeSceneLayerSource.h Wed Apr 22 14:05:47 2020 +0200 @@ -38,13 +38,14 @@ class VolumeSceneLayerSource : public boost::noncopyable { private: - Scene2D& scene_; - int layerDepth_; - boost::shared_ptr slicer_; + Scene2D& scene_; + int layerDepth_; + boost::shared_ptr slicer_; std::unique_ptr configurator_; std::unique_ptr lastPlane_; - uint64_t lastRevision_; - uint64_t lastConfiguratorRevision_; + uint64_t lastRevision_; + uint64_t lastConfiguratorRevision_; + bool layerInScene_; void ClearLayer(); @@ -71,6 +72,14 @@ ILayerStyleConfigurator& GetConfigurator() const; - void Update(const CoordinateSystem3D& plane); + /** + Make sure the Scene2D is protected from concurrent accesses before + calling this method. + + If the scene that has been supplied to the ctor is part of an IViewport, + you can lock the whole viewport data (including scene) by means of the + IViewport::Lock method. + */ + void Update(const CoordinateSystem3D& plane); }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/DelayedCallCommand.cpp --- a/Platforms/Generic/DelayedCallCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/DelayedCallCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -26,13 +26,11 @@ namespace Deprecated { - DelayedCallCommand::DelayedCallCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* callback, // takes ownership + DelayedCallCommand::DelayedCallCommand(MessageHandler* callback, // takes ownership unsigned int timeoutInMs, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context ) : - IObservable(broker), callback_(callback), payload_(payload), context_(context), diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/DelayedCallCommand.h --- a/Platforms/Generic/DelayedCallCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/DelayedCallCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -35,15 +35,14 @@ class DelayedCallCommand : public IOracleCommand, OrthancStone::IObservable { protected: - std::unique_ptr > callback_; + std::unique_ptr > callback_; std::unique_ptr payload_; OrthancStone::NativeStoneApplicationContext& context_; boost::posix_time::ptime expirationTimePoint_; unsigned int timeoutInMs_; public: - DelayedCallCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* callback, // takes ownership + DelayedCallCommand(MessageHandler* callback, // takes ownership unsigned int timeoutInMs, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/OracleDelayedCallExecutor.h --- a/Platforms/Generic/OracleDelayedCallExecutor.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/OracleDelayedCallExecutor.h Wed Apr 22 14:05:47 2020 +0200 @@ -36,19 +36,17 @@ OrthancStone::NativeStoneApplicationContext& context_; public: - OracleDelayedCallExecutor(OrthancStone::MessageBroker& broker, - Oracle& oracle, + OracleDelayedCallExecutor(Oracle& oracle, OrthancStone::NativeStoneApplicationContext& context) : - IDelayedCallExecutor(broker), oracle_(oracle), context_(context) { } - virtual void Schedule(OrthancStone::MessageHandler* callback, + virtual void Schedule(MessageHandler* callback, unsigned int timeoutInMs = 1000) { - oracle_.Submit(new DelayedCallCommand(broker_, callback, timeoutInMs, NULL, context_)); + oracle_.Submit(new DelayedCallCommand(callback, timeoutInMs, NULL, context_)); } }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/OracleWebService.cpp --- a/Platforms/Generic/OracleWebService.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/OracleWebService.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -29,19 +29,17 @@ class OracleWebService::WebServiceCachedGetCommand : public IOracleCommand, OrthancStone::IObservable { protected: - std::unique_ptr > successCallback_; + std::unique_ptr > successCallback_; std::unique_ptr payload_; boost::shared_ptr cachedMessage_; OrthancStone::NativeStoneApplicationContext& context_; public: - WebServiceCachedGetCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* successCallback, // takes ownership + WebServiceCachedGetCommand(MessageHandler* successCallback, // takes ownership boost::shared_ptr cachedMessage, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context ) : - IObservable(broker), successCallback_(successCallback), payload_(payload), cachedMessage_(cachedMessage), @@ -73,9 +71,9 @@ void OracleWebService::NotifyHttpSuccessLater(boost::shared_ptr cachedMessage, Orthanc::IDynamicObject* payload, // takes ownership - OrthancStone::MessageHandler* successCallback) + MessageHandler* successCallback) { - oracle_.Submit(new WebServiceCachedGetCommand(GetBroker(), successCallback, cachedMessage, payload, context_)); + oracle_.Submit(new WebServiceCachedGetCommand(successCallback, cachedMessage, payload, context_)); } diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/OracleWebService.h --- a/Platforms/Generic/OracleWebService.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/OracleWebService.h Wed Apr 22 14:05:47 2020 +0200 @@ -43,11 +43,9 @@ class WebServiceCachedGetCommand; public: - OracleWebService(OrthancStone::MessageBroker& broker, - Oracle& oracle, + OracleWebService(Oracle& oracle, const Orthanc::WebServiceParameters& parameters, OrthancStone::NativeStoneApplicationContext& context) : - BaseWebService(broker), oracle_(oracle), context_(context), parameters_(parameters) @@ -58,37 +56,37 @@ const HttpHeaders& headers, const std::string& body, Orthanc::IDynamicObject* payload, // takes ownership - OrthancStone::MessageHandler* successCallback, // takes ownership - OrthancStone::MessageHandler* failureCallback = NULL, // takes ownership + MessageHandler* successCallback, // takes ownership + MessageHandler* failureCallback = NULL, // takes ownership unsigned int timeoutInSeconds = 60) { - oracle_.Submit(new WebServicePostCommand(GetBroker(), successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, body, payload, context_)); + oracle_.Submit(new WebServicePostCommand(successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, body, payload, context_)); } virtual void DeleteAsync(const std::string& uri, const HttpHeaders& headers, Orthanc::IDynamicObject* payload, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback = NULL, + MessageHandler* successCallback, + MessageHandler* failureCallback = NULL, unsigned int timeoutInSeconds = 60) { - oracle_.Submit(new WebServiceDeleteCommand(GetBroker(), successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_)); + oracle_.Submit(new WebServiceDeleteCommand(successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_)); } protected: virtual void GetAsyncInternal(const std::string& uri, const HttpHeaders& headers, Orthanc::IDynamicObject* payload, // takes ownership - OrthancStone::MessageHandler* successCallback, // takes ownership - OrthancStone::MessageHandler* failureCallback = NULL,// takes ownership + MessageHandler* successCallback, // takes ownership + MessageHandler* failureCallback = NULL,// takes ownership unsigned int timeoutInSeconds = 60) { - oracle_.Submit(new WebServiceGetCommand(GetBroker(), successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_)); + oracle_.Submit(new WebServiceGetCommand(successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_)); } virtual void NotifyHttpSuccessLater(boost::shared_ptr cachedHttpMessage, Orthanc::IDynamicObject* payload, // takes ownership - OrthancStone::MessageHandler* successCallback); + MessageHandler* successCallback); }; } diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/WebServiceCommandBase.cpp --- a/Platforms/Generic/WebServiceCommandBase.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/WebServiceCommandBase.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -25,16 +25,14 @@ namespace Deprecated { - WebServiceCommandBase::WebServiceCommandBase(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* successCallback, - OrthancStone::MessageHandler* failureCallback, + WebServiceCommandBase::WebServiceCommandBase(MessageHandler* successCallback, + MessageHandler* failureCallback, const Orthanc::WebServiceParameters& parameters, const std::string& url, const IWebService::HttpHeaders& headers, unsigned int timeoutInSeconds, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context) : - IObservable(broker), successCallback_(successCallback), failureCallback_(failureCallback), parameters_(parameters), diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/WebServiceCommandBase.h --- a/Platforms/Generic/WebServiceCommandBase.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/WebServiceCommandBase.h Wed Apr 22 14:05:47 2020 +0200 @@ -37,8 +37,8 @@ class WebServiceCommandBase : public IOracleCommand, OrthancStone::IObservable { protected: - std::unique_ptr > successCallback_; - std::unique_ptr > failureCallback_; + std::unique_ptr > successCallback_; + std::unique_ptr > failureCallback_; Orthanc::WebServiceParameters parameters_; std::string url_; IWebService::HttpHeaders headers_; @@ -51,9 +51,8 @@ unsigned int timeoutInSeconds_; public: - WebServiceCommandBase(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* successCallback, // takes ownership - OrthancStone::MessageHandler* failureCallback, // takes ownership + WebServiceCommandBase(MessageHandler* successCallback, // takes ownership + MessageHandler* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url, const IWebService::HttpHeaders& headers, diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/WebServiceDeleteCommand.cpp --- a/Platforms/Generic/WebServiceDeleteCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/WebServiceDeleteCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -25,16 +25,15 @@ namespace Deprecated { - WebServiceDeleteCommand::WebServiceDeleteCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* successCallback, // takes ownership - OrthancStone::MessageHandler* failureCallback, // takes ownership + WebServiceDeleteCommand::WebServiceDeleteCommand(MessageHandler* successCallback, // takes ownership + MessageHandler* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url, const Deprecated::IWebService::HttpHeaders& headers, unsigned int timeoutInSeconds, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context) : - WebServiceCommandBase(broker, successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context) + WebServiceCommandBase(successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context) { } diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/WebServiceDeleteCommand.h --- a/Platforms/Generic/WebServiceDeleteCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/WebServiceDeleteCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -28,9 +28,8 @@ class WebServiceDeleteCommand : public WebServiceCommandBase { public: - WebServiceDeleteCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* successCallback, // takes ownership - OrthancStone::MessageHandler* failureCallback, // takes ownership + WebServiceDeleteCommand(MessageHandler* successCallback, // takes ownership + MessageHandler* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url, const IWebService::HttpHeaders& headers, diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/WebServiceGetCommand.cpp --- a/Platforms/Generic/WebServiceGetCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/WebServiceGetCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -25,17 +25,15 @@ namespace Deprecated { - - WebServiceGetCommand::WebServiceGetCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* successCallback, // takes ownership - OrthancStone::MessageHandler* failureCallback, // takes ownership + WebServiceGetCommand::WebServiceGetCommand(MessageHandler* successCallback, // takes ownership + MessageHandler* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url, const IWebService::HttpHeaders& headers, unsigned int timeoutInSeconds, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context) : - WebServiceCommandBase(broker, successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context) + WebServiceCommandBase(successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context) { } diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/WebServiceGetCommand.h --- a/Platforms/Generic/WebServiceGetCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/WebServiceGetCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -28,9 +28,8 @@ class WebServiceGetCommand : public WebServiceCommandBase { public: - WebServiceGetCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* successCallback, // takes ownership - OrthancStone::MessageHandler* failureCallback, // takes ownership + WebServiceGetCommand(MessageHandler* successCallback, // takes ownership + MessageHandler* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url, const IWebService::HttpHeaders& headers, diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/WebServicePostCommand.cpp --- a/Platforms/Generic/WebServicePostCommand.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/WebServicePostCommand.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -25,9 +25,8 @@ namespace Deprecated { - WebServicePostCommand::WebServicePostCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* successCallback, // takes ownership - OrthancStone::MessageHandler* failureCallback, // takes ownership + WebServicePostCommand::WebServicePostCommand(MessageHandler* successCallback, // takes ownership + MessageHandler* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url, const Deprecated::IWebService::HttpHeaders& headers, @@ -35,7 +34,7 @@ const std::string& body, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context) : - WebServiceCommandBase(broker, successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context), + WebServiceCommandBase(successCallback, failureCallback, parameters, url, headers, timeoutInSeconds, payload, context), body_(body) { } diff -r 4431ffdcc2a4 -r f4a06ad1580b Platforms/Generic/WebServicePostCommand.h --- a/Platforms/Generic/WebServicePostCommand.h Mon Apr 20 18:26:32 2020 +0200 +++ b/Platforms/Generic/WebServicePostCommand.h Wed Apr 22 14:05:47 2020 +0200 @@ -31,9 +31,8 @@ std::string body_; public: - WebServicePostCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler* successCallback, // takes ownership - OrthancStone::MessageHandler* failureCallback, // takes ownership + WebServicePostCommand(MessageHandler* successCallback, // takes ownership + MessageHandler* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url, const IWebService::HttpHeaders& headers, diff -r 4431ffdcc2a4 -r f4a06ad1580b README.md --- a/README.md Mon Apr 20 18:26:32 2020 +0200 +++ b/README.md Wed Apr 22 14:05:47 2020 +0200 @@ -91,133 +91,11 @@ http://book.orthanc-server.com/developers/stone.html Stone of Orthanc comes with several sample applications in the -`Samples` folder. These samples can be compiled into Web Assembly or -into native SDL applications. - -The following assumes that the source code to be downloaded in -`~/orthanc-stone` and Orthanc source code to be checked out in -`~/orthanc`. - -Building the WASM samples -------------------------------------- -``` -cd ~/orthanc-stone/Applications/Samples -./build-wasm.sh -``` - -Serving the WASM samples ------------------------------------- -``` -# launch an Orthanc listening on 8042 port: -Orthanc - -# launch an nginx that will serve the WASM static files and reverse -# proxy -sudo nginx -p $(pwd) -c nginx.local.conf -``` - -You can now open the samples in http://localhost:9977 - -Building the SDL native samples (SimpleViewer only) ---------------------------------------------------- - -The following also assumes that you have checked out the Orthanc -source code in an `orthanc` folder next to the Stone of Orthanc -repository, please enter the following: - -**Simple make generator with dynamic build** - -``` -# Please set $currentDir to the current folder -mkdir -p ~/builds/orthanc-stone-build -cd ~/builds/orthanc-stone-build -cmake -DORTHANC_FRAMEWORK_SOURCE=path \ - -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc \ - -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON \ - ~/orthanc-stone/Applications/Samples/ -``` - -**Ninja generator with static SDL build (pwsh script)** - -``` -# Please yourself one level above the orthanc-stone and orthanc folders -if( -not (test-path stone_build_sdl)) { mkdir stone_build_sdl } -cd stone_build_sdl -cmake -G Ninja -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples/ -``` - -**Ninja generator with static SDL build (bash/zsh script)** - -``` -# Please yourself one level above the orthanc-stone and orthanc folders -if( -not (test-path stone_build_sdl)) { mkdir stone_build_sdl } -cd stone_build_sdl -cmake -G Ninja -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="`pwd`/../orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples/ -``` +`Samples` folder. -**Visual Studio 2017 generator with static SDL build (pwsh script)** - -``` -# The following will use Visual Studio 2017 to build the SDL samples -# in debug mode (with multiple compilers in parallel). NOTE: place -# yourself one level above the `orthanc-stone` and `orthanc` folders - -if( -not (test-path stone_build_sdl)) { mkdir stone_build_sdl } -cd stone_build_sdl -cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples/ -cmake --build . --config Debug -``` - -If you are working on Windows, add the correct generator option to -cmake to, for instance, generate msbuild files for Visual Studio. - -Then, under Linux: -``` -cmake --build . --target OrthancStoneSimpleViewer -- -j 5 -``` - -Note: replace `$($pwd)` with the current directory when not using Powershell - -Building the Qt native samples (SimpleViewer only) under Windows: ------------------------------------------------------------------- - -**Visual Studio 2017 generator with static Qt build (pwsh script)** - -For instance, if Qt is installed in `C:\Qt\5.12.0\msvc2017_64` +Please see the Samples/README.md file for information on the samples building +and running procedure. -``` -# The following will use Visual Studio 2017 to build the SDL samples -# in debug mode (with multiple compilers in parallel). NOTE: place -# yourself one level above the `orthanc-stone` and `orthanc` folders - -if( -not (test-path stone_build_qt)) { mkdir stone_build_qt } -cd stone_build_qt -cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DCMAKE_PREFIX_PATH=C:\Qt\5.12.0\msvc2017_64 -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_QT=ON ../orthanc-stone/Applications/Samples/ -cmake --build . --config Debug -``` - -Note: replace `$($pwd)` with the current directory when not using Powershell - - - - - - -Building the SDL native samples (SimpleViewer only) under Windows: ------------------------------------------------------------------- -`cmake -DSTATIC_BUILD=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON -G "Visual Studio 15 2017 Win64" ../orthanc-stone/Applications/Samples/` - -Note: replace `$($pwd)` with the current directory when not using Powershell - -Executing the native samples: --------------------------------- -``` -# launch an Orthanc listening on 8042 port: -Orthanc - -# launch the sample -./OrthancStoneSimpleViewer --studyId=XX -``` Licensing --------- @@ -245,59 +123,3 @@ url="https://doi.org/10.1007/s10278-018-0082-y" } -Build the Application Samples ------------------------------ - -**Visual Studio 2008 (v90) ** - -``` -cmake -G "Visual Studio 9 2008" -DUSE_LEGACY_JSONCPP=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples -``` - -**Visual Studio 2019 (v142) ** - -``` -cmake -G "Visual Studio 16 2019" -A x64 -DMSVC_MULTIPLE_PROCESSES=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples -``` - -**Visual Studio 2017 (v140) ** - -``` -cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples -``` - - -Build the core Samples ---------------------------- -How to build the newest (2019-04-29) SDL samples under Windows, *inside* a -folder that is sibling to the orthanc-stone folder: - -**Visual Studio 2019 (v142) ** - -``` -cmake -G "Visual Studio 16 2019" -A x64 -DMSVC_MULTIPLE_PROCESSES=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl -``` - -**Visual Studio 2017 (v140) ** - -``` -cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl -``` - -**Visual Studio 2008 (v90) ** - -``` -cmake -G "Visual Studio 9 2008" -DUSE_LEGACY_JSONCPP=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl -``` - -And under Ubuntu (note the /mnt/c/osi/dev/orthanc folder): -``` -cmake -G "Ninja" -DENABLE_OPENGL=ON -DSTATIC_BUILD=OFF -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="/mnt/c/osi/dev/orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl -``` - -TODO trackers: -- CANCELLED (using outlined text now) text overlay 50% --> ColorTextureLayer 50% -- DONE angle tracker: draw arcs -- Handles on arc -- Select measure tool with hit test --> Delete command - diff -r 4431ffdcc2a4 -r f4a06ad1580b Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Mon Apr 20 18:26:32 2020 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Wed Apr 22 14:05:47 2020 +0200 @@ -121,12 +121,16 @@ -DORTHANC_ENABLE_SDL=1 ) elseif(ENABLE_QT) - message("QT is enabled") - include(${CMAKE_CURRENT_LIST_DIR}/QtConfiguration.cmake) add_definitions( -DORTHANC_ENABLE_QT=1 -DORTHANC_ENABLE_SDL=0 ) + if(DISABLE_STONE_QT_CMAKE_FILE) + message("QT is enabled, but QtConfiguration.cmake will not be included") + else() + message("QT is enabled") + include(${CMAKE_CURRENT_LIST_DIR}/QtConfiguration.cmake) + endif() else() message("SDL and QT are both disabled") unset(USE_SYSTEM_SDL CACHE) @@ -250,61 +254,43 @@ ## All the source files required to build Stone of Orthanc ##################################################################### -set(APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/IStoneApplication.h - ) - if (NOT ORTHANC_SANDBOXED) set(PLATFORM_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Loaders/GenericLoadersContext.cpp + ${ORTHANC_STONE_ROOT}/Framework/Loaders/GenericLoadersContext.h + ${ORTHANC_STONE_ROOT}/Platforms/Generic/DelayedCallCommand.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Generic/DelayedCallCommand.h + ${ORTHANC_STONE_ROOT}/Platforms/Generic/Oracle.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Generic/Oracle.h + ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleDelayedCallExecutor.h ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceCommandBase.cpp - ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.cpp - ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceCommandBase.h ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceDeleteCommand.cpp - ${ORTHANC_STONE_ROOT}/Platforms/Generic/DelayedCallCommand.cpp - ${ORTHANC_STONE_ROOT}/Platforms/Generic/Oracle.cpp - ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleDelayedCallExecutor.h + ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceDeleteCommand.h + ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.h + ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.cpp + ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.h ) - if (ENABLE_STONE_DEPRECATED) - list(APPEND PLATFORM_SOURCES - ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleWebService.cpp - ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/CairoFont.cpp + if (ENABLE_SDL) + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlWindow.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlWindow.h ) endif() if (ENABLE_SDL OR ENABLE_QT) - if (ENABLE_STONE_DEPRECATED) - list(APPEND APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationRunner.cpp - ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationContext.cpp - ) - endif() - - if (ENABLE_SDL) + if (ENABLE_OPENGL) list(APPEND ORTHANC_STONE_SOURCES - ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlWindow.cpp + ${ORTHANC_STONE_ROOT}/Framework/OpenGL/SdlOpenGLContext.cpp + ${ORTHANC_STONE_ROOT}/Framework/OpenGL/SdlOpenGLContext.h + ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlViewport.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlViewport.h ) - - list(APPEND APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp - ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlEngine.cpp - ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOrthancSurface.cpp - ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlStoneApplicationRunner.cpp - ) - - if (ENABLE_OPENGL) - list(APPEND ORTHANC_STONE_SOURCES - ${ORTHANC_STONE_ROOT}/Framework/OpenGL/SdlOpenGLContext.cpp - ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlViewport.cpp - ) - endif() endif() endif() elseif (ENABLE_WASM) - list(APPEND APPLICATIONS_SOURCES - ${ORTHANC_STONE_ROOT}/Applications/Wasm/StartupParametersBuilder.cpp - ) - set(STONE_WASM_SOURCES ${ORTHANC_STONE_ROOT}/Platforms/Wasm/Defaults.cpp ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmDelayedCallExecutor.cpp @@ -331,60 +317,138 @@ 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) + if (NOT ORTHANC_SANDBOXED) + list(APPEND PLATFORM_SOURCES + ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleWebService.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/CairoFont.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Viewport/CairoFont.h + ) + endif() + + if (ENABLE_SDL OR ENABLE_QT) + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationRunner.cpp + ${ORTHANC_STONE_ROOT}/Applications/Generic/NativeStoneApplicationContext.cpp + ) + endif() + + if (ENABLE_SDL) + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlEngine.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOrthancSurface.cpp + ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlStoneApplicationRunner.cpp + ) + endif() -if (ENABLE_STONE_DEPRECATED) + if (ENABLE_WASM) + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Wasm/StartupParametersBuilder.cpp + ) + endif() + + if (ENABLE_THREADS) + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Messages/LockingEmitter.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Messages/LockingEmitter.h + ) + endif() + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/IStoneApplication.h ${ORTHANC_STONE_ROOT}/Applications/StoneApplicationContext.cpp + + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/dev.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/SmartLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/SmartLoader.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/CircleMeasureTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/CircleMeasureTracker.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/ColorFrameRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/ColorFrameRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/DicomStructureSetSlicer.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/FrameRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/FrameRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/GrayscaleFrameRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/GrayscaleFrameRenderer.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/ILayerRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/IVolumeSlicer.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/LineLayerRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/LineLayerRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/LineMeasureTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/LineMeasureTracker.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/RenderStyle.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/RenderStyle.h + # ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/SeriesFrameRendererFactory.cpp + # ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/SeriesFrameRendererFactory.h + # ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/SingleFrameRendererFactory.cpp + # ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/SingleFrameRendererFactory.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/SliceOutlineRenderer.cpp - ${ORTHANC_STONE_ROOT}/Framework/Deprecated/SmartLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/SliceOutlineRenderer.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/DicomStructureSetLoader2.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Loaders/DicomStructureSetLoader2.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/BaseWebService.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/BaseWebService.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DicomFrameConverter.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DicomFrameConverter.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DownloadStack.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DownloadStack.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ISeriesLoader.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IWebService.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IWebService.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/MessagingToolbox.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/MessagingToolbox.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancApiClient.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancApiClient.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancSlicesLoader.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ParallelSlices.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ParallelSlices.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ParallelSlicesCursor.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ParallelSlicesCursor.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/Slice.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/Slice.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ViewportGeometry.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/ViewportGeometry.h ${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/Viewport/WidgetViewport.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Volumes/ISlicedVolume.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Volumes/IVolumeLoader.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Volumes/StructureSetLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Volumes/StructureSetLoader.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/CairoWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/CairoWidget.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/EmptyWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/EmptyWidget.h ${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/LayoutWidget.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/PanMouseTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/PanMouseTracker.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/PanZoomMouseTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/PanZoomMouseTracker.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/SliceViewerWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/SliceViewerWidget.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/TestCairoWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/TestCairoWidget.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/TestWorldSceneWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/TestWorldSceneWidget.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/WidgetBase.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/WidgetBase.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/WorldSceneWidget.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/WorldSceneWidget.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/ZoomMouseTracker.cpp - ${ORTHANC_STONE_ROOT}/Framework/Deprecated/dev.h + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Widgets/ZoomMouseTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyAlphaLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyDicomLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp @@ -405,20 +469,39 @@ endif() +if (ENABLE_DCMTK) + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomSuccessMessage.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParsedDicomCache.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParsedDicomDataset.cpp + ) +endif() + if (ENABLE_THREADS) list(APPEND ORTHANC_STONE_SOURCES - ${ORTHANC_STONE_ROOT}/Framework/Messages/LockingEmitter.h ${ORTHANC_STONE_ROOT}/Framework/Oracle/ThreadedOracle.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/GenericOracleRunner.cpp ) endif() if (ENABLE_WASM) list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/Loaders/WebAssemblyLoadersContext.cpp ${ORTHANC_STONE_ROOT}/Framework/Oracle/WebAssemblyOracle.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/WebAssemblyCairoViewport.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/WebAssemblyViewport.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/WebAssemblyViewport.h ) 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() + list(APPEND ORTHANC_STONE_SOURCES #${ORTHANC_STONE_ROOT}/Framework/Layers/SeriesFrameRendererFactory.cpp @@ -441,57 +524,102 @@ ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingItemsSorter.h ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingStrategy.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/BasicFetchingStrategy.h + ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomResourcesLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomSource.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader.h - ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader2.cpp - ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomStructureSetLoader2.h + ${ORTHANC_STONE_ROOT}/Framework/Loaders/DicomVolumeLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/IFetchingItemsSorter.h ${ORTHANC_STONE_ROOT}/Framework/Loaders/IFetchingStrategy.h + ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoadedDicomResources.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderCache.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderCache.h ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderStateMachine.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/LoaderStateMachine.h ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancMultiframeVolumeLoader.h + ${ORTHANC_STONE_ROOT}/Framework/Loaders/OracleScheduler.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h - + ${ORTHANC_STONE_ROOT}/Framework/Loaders/SeriesFramesLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Loaders/SeriesMetadataLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Loaders/SeriesOrderedFrames.cpp + ${ORTHANC_STONE_ROOT}/Framework/Loaders/SeriesThumbnailsLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h + ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessageEmitter.h ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.cpp - ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.cpp + ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.h ${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/ObserverBase.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/HttpCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/OracleCommandBase.cpp ${ORTHANC_STONE_ROOT}/Framework/Oracle/OrthancRestApiCommand.cpp - ${ORTHANC_STONE_ROOT}/Framework/Oracle/HttpCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomFromFileCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomFromWadoCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/CairoCompositor.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/CairoCompositor.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Color.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ColorSceneLayer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ColorTextureSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ColorTextureSceneLayer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/FloatTextureSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/FloatTextureSceneLayer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/GrayscaleStyleConfigurator.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/GrayscaleStyleConfigurator.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ICompositor.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ILayerStyleConfigurator.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/InfoPanelSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/InfoPanelSceneLayer.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/IPointerTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ISceneLayer.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/LookupTableStyleConfigurator.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/LookupTableStyleConfigurator.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/LookupTableTextureSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/LookupTableTextureSceneLayer.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/NullLayer.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PanSceneTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PanSceneTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PointerEvent.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PointerEvent.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PolylineSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/PolylineSceneLayer.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/RotateSceneTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/RotateSceneTracker.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Scene2D.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Scene2D.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ScenePoint2D.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextSceneLayer.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextureBaseSceneLayer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextureBaseSceneLayer.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ZoomSceneTracker.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ZoomSceneTracker.h + + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoBaseRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoColorTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoColorTextureRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoFloatTextureRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoInfoPanelRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoInfoPanelRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoPolylineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoPolylineRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoTextRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CairoTextRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CompositorHelper.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/CompositorHelper.h ${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/Scene2D/Internals/FixedPointAligner.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/ICairoContextProvider.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.cpp ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.h ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureCommand.cpp @@ -588,9 +716,8 @@ ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/IViewport.h - ${ORTHANC_STONE_ROOT}/Framework/Viewport/ViewportBase.h - ${ORTHANC_STONE_ROOT}/Framework/Viewport/ViewportBase.cpp + ${ORTHANC_STONE_ROOT}/Framework/Volumes/IGeometryProvider.h ${ORTHANC_STONE_ROOT}/Framework/Volumes/IVolumeSlicer.cpp ${ORTHANC_STONE_ROOT}/Framework/Volumes/IVolumeSlicer.h ${ORTHANC_STONE_ROOT}/Framework/Volumes/OrientedVolumeBoundingBox.cpp @@ -648,38 +775,40 @@ ${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.cpp ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLAdvancedPolylineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLBasicPolylineRenderer.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/OpenGLColorTextureProgram.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureRenderer.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLColorTextureRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLFloatTextureProgram.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/OpenGLFloatTextureRenderer.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLInfoPanelRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLLinesProgram.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/OpenGLLookupTableTextureRenderer.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLShaderVersionDirective.h + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextProgram.cpp ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextProgram.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextProgram.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextRenderer.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextureProgram.cpp ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextureProgram.h - ${ORTHANC_STONE_ROOT}/Framework/Scene2D/Internals/OpenGLTextureProgram.cpp ) if (ENABLE_WASM) list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Framework/OpenGL/WebAssemblyOpenGLContext.cpp ${ORTHANC_STONE_ROOT}/Framework/OpenGL/WebAssemblyOpenGLContext.h - ${ORTHANC_STONE_ROOT}/Framework/OpenGL/WebAssemblyOpenGLContext.cpp - ${ORTHANC_STONE_ROOT}/Framework/Viewport/WebAssemblyViewport.h - ${ORTHANC_STONE_ROOT}/Framework/Viewport/WebAssemblyViewport.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/WebGLViewport.cpp + ${ORTHANC_STONE_ROOT}/Framework/Viewport/WebGLViewportsRegistry.cpp ) endif() endif() diff -r 4431ffdcc2a4 -r f4a06ad1580b Resources/CMake/QtConfiguration.cmake --- a/Resources/CMake/QtConfiguration.cmake Mon Apr 20 18:26:32 2020 +0200 +++ b/Resources/CMake/QtConfiguration.cmake Wed Apr 22 14:05:47 2020 +0200 @@ -20,26 +20,12 @@ set(CMAKE_AUTOMOC OFF) set(CMAKE_AUTOUIC OFF) -if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") - # Linux Standard Base version 5 ships Qt 4.2.3 + +## Note that these set of macros MUST be defined as a "function()", +## otherwise it fails +function(DEFINE_QT_MACROS) include(Qt4Macros) - # The script "LinuxStandardBaseUic.py" is just a wrapper around the - # "uic" compiler from LSB that does not support the "" - # header that is automatically added by Qt Creator - set(QT_UIC_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/LinuxStandardBaseUic.py) - - set(QT_MOC_EXECUTABLE ${LSB_PATH}/bin/moc) - - include_directories( - ${LSB_PATH}/include/QtCore - ${LSB_PATH}/include/QtGui - ${LSB_PATH}/include/QtOpenGL - ) - - link_libraries(QtCore QtGui QtOpenGL) - - ## ## This part is adapted from file "Qt4Macros.cmake" shipped with ## CMake 3.5.1, released under the following license: @@ -86,9 +72,91 @@ ## ## End of "Qt4Macros.cmake" adaptation. ## +endfunction() + +if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase") + # Linux Standard Base version 5 ships Qt 4.2.3 + DEFINE_QT_MACROS() + + # The script "LinuxStandardBaseUic.py" is just a wrapper around the + # "uic" compiler from LSB that does not support the "" + # header that is automatically added by Qt Creator + set(QT_UIC_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/LinuxStandardBaseUic.py) + + set(QT_MOC_EXECUTABLE ${LSB_PATH}/bin/moc) + + include_directories( + ${LSB_PATH}/include/QtCore + ${LSB_PATH}/include/QtGui + ${LSB_PATH}/include/QtOpenGL + ) + + link_libraries(QtCore QtGui QtOpenGL) + +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + DEFINE_QT_MACROS() + + include_directories(${QT5_INSTALL_ROOT}/include) + link_directories(${QT5_INSTALL_ROOT}/lib) + + if (OFF) #CMAKE_CROSSCOMPILING) + set(QT_UIC_EXECUTABLE wine ${QT5_INSTALL_ROOT}/bin/uic.exe) + set(QT_MOC_EXECUTABLE wine ${QT5_INSTALL_ROOT}/bin/moc.exe) + else() + set(QT_UIC_EXECUTABLE ${QT5_INSTALL_ROOT}/bin/uic) + set(QT_MOC_EXECUTABLE ${QT5_INSTALL_ROOT}/bin/moc) + endif() + + include_directories( + ${QT5_INSTALL_ROOT}/include/QtCore + ${QT5_INSTALL_ROOT}/include/QtGui + ${QT5_INSTALL_ROOT}/include/QtOpenGL + ${QT5_INSTALL_ROOT}/include/QtWidgets + ) + + if (OFF) + # Dynamic Qt + link_libraries(Qt5Core Qt5Gui Qt5OpenGL Qt5Widgets) + + file(COPY + ${QT5_INSTALL_ROOT}/bin/Qt5Core.dll + ${QT5_INSTALL_ROOT}/bin/Qt5Gui.dll + ${QT5_INSTALL_ROOT}/bin/Qt5OpenGL.dll + ${QT5_INSTALL_ROOT}/bin/Qt5Widgets.dll + ${QT5_INSTALL_ROOT}/bin/libstdc++-6.dll + ${QT5_INSTALL_ROOT}/bin/libgcc_s_dw2-1.dll + ${QT5_INSTALL_ROOT}/bin/libwinpthread-1.dll + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + + file(COPY + ${QT5_INSTALL_ROOT}/plugins/platforms/qwindows.dll + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/platforms) + + else() + # Static Qt + link_libraries( + ${QT5_INSTALL_ROOT}/lib/libQt5Widgets.a + ${QT5_INSTALL_ROOT}/lib/libQt5Gui.a + ${QT5_INSTALL_ROOT}/lib/libQt5OpenGL.a + ${QT5_INSTALL_ROOT}/lib/libQt5Core.a + ${QT5_INSTALL_ROOT}/lib/libqtharfbuzz.a + ${QT5_INSTALL_ROOT}/lib/libqtpcre2.a + ${QT5_INSTALL_ROOT}/lib/libQt5FontDatabaseSupport.a + ${QT5_INSTALL_ROOT}/lib/libQt5EventDispatcherSupport.a + ${QT5_INSTALL_ROOT}/lib/libQt5ThemeSupport.a + ${QT5_INSTALL_ROOT}/plugins/platforms/libqwindows.a + winmm + version + ws2_32 + uxtheme + imm32 + dwmapi + ) + endif() + else() - # Not using Linux Standard Base + # Not using Windows, not using Linux Standard Base, # Find the QtWidgets library find_package(Qt5Widgets QUIET) diff -r 4431ffdcc2a4 -r f4a06ad1580b Resources/CMake/Utilities.cmake --- a/Resources/CMake/Utilities.cmake Mon Apr 20 18:26:32 2020 +0200 +++ b/Resources/CMake/Utilities.cmake Wed Apr 22 14:05:47 2020 +0200 @@ -35,3 +35,50 @@ endmacro() Test_GetFilenameFromPath() + +macro(SortFilesInSourceGroups) + if(FALSE) + foreach(source IN LISTS ORTHANC_STONE_SOURCES) + # if("${source}" MATCHES ".*/pixman.*\\.c") + # message("pixman source: ${source}") + # elseif("${source}" MATCHES ".*/pixman.*\\.c") + # message("pixman header: ${source}") + # endif() + + if("${source}" MATCHES ".*\\.\\./.*") + message("source raw: ${source}") + #file(TO_CMAKE_PATH ${source} sourceCMakePath) + get_filename_component(sourceCMakePath ${source} ABSOLUTE) + message("source CMake: ${sourceCMakePath}") + endif() + + # returns the containing directory with forward slashes + # get_filename_component(source_path "${source}" PATH) + + # converts / to \ + # string(REPLACE "/" "\\" source_path_msvc "${source_path}") + #source_group("Stone ${source_path_msvc}" FILES "${source}") + endforeach() + endif() + + source_group("Orthanc Framework\\Sources" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*cpp") + source_group("Orthanc Framework\\Headers" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*hpp") + source_group("Orthanc Framework\\Headers" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*h") + + source_group("Stone Library\\Sources" REGULAR_EXPRESSION ".*/orthanc-stone/.*cpp") + source_group("Stone Library\\Headers" REGULAR_EXPRESSION ".*/orthanc-stone/.*hpp") + source_group("Stone Library\\Headers" REGULAR_EXPRESSION ".*/orthanc-stone/.*h") + + source_group("Stone Samples\\Source" REGULAR_EXPRESSION ".*orthanc-stone/Samples/.*\\.cpp") + source_group("Stone Samples\\Headers" REGULAR_EXPRESSION ".*orthanc-stone/Samples/.*\\.h") + + source_group("ThirdParty\\cairo" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/cairo[^/]*/.*") + source_group("ThirdParty\\pixman" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/pixman[^/]*/.*") + source_group("ThirdParty\\base64" REGULAR_EXPRESSION ".*ThirdParty/base64.*") + source_group("ThirdParty\\SDL2" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/SDL2.*") + source_group("ThirdParty\\glew" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/glew.*") + source_group("AUTOGENERATED" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/AUTOGENERATED/.*") + source_group("ThirdParty\\minizip" REGULAR_EXPRESSION ".*ThirdParty/minizip/.*") + source_group("ThirdParty\\md5" REGULAR_EXPRESSION ".*ThirdParty/md5/.*") +endmacro() + diff -r 4431ffdcc2a4 -r f4a06ad1580b Resources/Conventions.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Conventions.txt Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,88 @@ + +Some notes about the lifetime of objects +======================================== + +Stone applications +------------------ + +A typical Stone application can be split in 3 parts: + +1- The "loaders part" and the associated "IOracle", that communicate + through "IMessage" objects. The lifetime of these objects is + governed by the "IStoneContext". + +2- The "data part" holds the data loaded by the "loaders part". The + related objects must not be aware of the oracle, neither of the + messages. It is up to the user application to store these objects. + +3- The "viewport part" is based upon the "Scene2D" class. + + +Multithreading +-------------- + +* Stone makes the hypothesis that its objects live in a single thread. + All the content of the "Framework" folder (with the exception of + the "Oracle" stuff) must not use "boost::thread". + +* The "IOracleCommand" classes represent commands that must be + executed asynchronously from the Stone thread. Their actual + execution is done by the "IOracle". + +* In WebAssembly, the "IOracle" corresponds to the "html5.h" + facilities (notably for the Fetch API). There is no mutex here, as + JavaScript is inherently single-threaded. + +* In plain C++ applications, the "IOracle" corresponds to a FIFO queue + of commands that are executed by a pool of threads. The Stone + context holds a global mutex, that must be properly locked by the + user application, and by the "IOracle" when it sends back messages + to the Stone loaders (cf. class "IMessageEmitter"). + +* Multithreading is thus achieved by defining new oracle commands by + subclassing "IOracleCommand", then by defining a way to execute them + (cf. class "GenericCommandRunner"). + + +References between objects +-------------------------- + +* An object allocated on the heap must never store a reference/pointer + to another object. + +* A class designed to be allocated only on the stack can store a + reference/pointer to another object. Here is the list of + such classes: + + - IMessage and its derived classes: All the messages are allocated + on the stack. + + +Pointers +-------- + +* As we are targeting C++03 (for VS2008 and LSB compatibility), use + "std::unique_ptr<>" and "boost::shared_ptr<>" (*not* + "std::shared_ptr<>"). We provide an implementation of std::unique_ptr for + pre-C++11 compilers. + +* The fact of transfering the ownership of one object to another must + be tagged by naming the method "Acquire...()", and by providing a + raw pointer. + +* Use "std::unique_ptr<>" if the goal is to internally store a pointer + whose lifetime corresponds to the host object. + +* The use of "boost::weak_ptr<>" should be restricted to + oracle/message handling. + +* The use of "boost::shared_ptr<>" should be minimized to avoid + clutter. The "loaders" and "data parts" objects must however + be created as "boost::shared_ptr<>". + + +Global context +-------------- + +* As the global Stone context can be created/destroyed by other + languages than C++, we don't use a "boost:shared_ptr<>". diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/MultiPlatform/BasicScene/BasicScene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/MultiPlatform/BasicScene/BasicScene.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,275 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "BasicScene.h" + +// From Stone +#include "Framework/Scene2D/Scene2D.h" +#include "Framework/Scene2D/ColorTextureSceneLayer.h" +#include "Framework/Scene2D/PolylineSceneLayer.h" +#include "Framework/Scene2D/TextSceneLayer.h" + +#include "Framework/Scene2D/PanSceneTracker.h" +#include "Framework/Scene2D/ZoomSceneTracker.h" +#include "Framework/Scene2D/RotateSceneTracker.h" + +#include "Framework/Scene2D/CairoCompositor.h" + +// From Orthanc framework +#include +#include +#include + +using namespace OrthancStone; + +const unsigned int BASIC_SCENE_FONT_SIZE = 32; +const int BASIC_SCENE_LAYER_POSITION = 150; + +void PrepareScene(Scene2D& scene) +{ + //Scene2D& scene(*controller->GetScene()); + // Texture of 2x2 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast(i.GetRow(1)); + p[0] = 0; + p[1] = 0; + p[2] = 255; + + p[3] = 255; + p[4] = 0; + p[5] = 0; + + scene.SetLayer(12, new ColorTextureSceneLayer(i)); + + std::unique_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-3, 2); + l->SetPixelSpacing(1.5, 1); + l->SetAngle(20.0 / 180.0 * 3.14); + scene.SetLayer(14, l.release()); + } + + // Texture of 1x1 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + std::unique_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-2, 1); + l->SetAngle(20.0 / 180.0 * 3.14); + scene.SetLayer(13, l.release()); + } + + // Some lines + { + std::unique_ptr layer(new PolylineSceneLayer); + + layer->SetThickness(1); + + 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, 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, 0, 255, 0); + + double dy = 1.01; + chain.clear(); + chain.push_back(ScenePoint2D(-4, -4)); + 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, 0, 0, 255); + + // layer->SetColor(0,255, 255); + scene.SetLayer(50, layer.release()); + } + + // Some text + { + std::unique_ptr layer(new TextSceneLayer); + layer->SetText("Hello"); + scene.SetLayer(100, layer.release()); + } +} + +#if ORTHANC_SANDBOXED == 0 +void TakeScreenshot(const std::string& target, + 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, BASIC_SCENE_FONT_SIZE, 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); +} +#endif + +void ShowCursorInfo(Scene2D& scene, const PointerEvent& pointerEvent) +{ + ScenePoint2D p = pointerEvent.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); + + char buf[64]; + sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY()); + + if (scene.HasLayer(BASIC_SCENE_LAYER_POSITION)) + { + TextSceneLayer& layer = + dynamic_cast(scene.GetLayer(BASIC_SCENE_LAYER_POSITION)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::unique_ptr + layer(new TextSceneLayer); + layer->SetColor(0, 255, 0); + layer->SetText(buf); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_BottomCenter); + layer->SetPosition(p.GetX(), p.GetY()); + scene.SetLayer(BASIC_SCENE_LAYER_POSITION, layer.release()); + } +} + + + +bool BasicScene2DInteractor::OnMouseEvent(const GuiAdapterMouseEvent& event, const PointerEvent& pointerEvent) +{ + if (currentTracker_.get() != NULL) + { + switch (event.type) + { + case GUIADAPTER_EVENT_MOUSEUP: + { + currentTracker_->PointerUp(pointerEvent); + if (!currentTracker_->IsAlive()) + { + currentTracker_.reset(); + } + };break; + case GUIADAPTER_EVENT_MOUSEMOVE: + { + currentTracker_->PointerMove(pointerEvent); + };break; + default: + return false; + } + return true; + } + else if (event.type == GUIADAPTER_EVENT_MOUSEDOWN) + { + if (event.button == GUIADAPTER_MOUSEBUTTON_LEFT) + { + currentTracker_.reset(new RotateSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_MIDDLE) + { + currentTracker_.reset(new PanSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_RIGHT) + { + currentTracker_.reset(new ZoomSceneTracker(viewportController_, pointerEvent, viewportController_->GetViewport().GetCanvasHeight())); + } + } + else if (event.type == GUIADAPTER_EVENT_MOUSEMOVE) + { + if (showCursorInfo_) + { + Scene2D& scene(viewportController_->GetScene()); + ShowCursorInfo(scene, pointerEvent); + } + return true; + } + return false; +} + +bool BasicScene2DInteractor::OnKeyboardEvent(const GuiAdapterKeyboardEvent& guiEvent) +{ + if (guiEvent.type == GUIADAPTER_EVENT_KEYDOWN) + { + switch (guiEvent.sym[0]) + { + case 's': + { + //viewportController_->FitContent(viewportController_->GetViewport().GetCanvasWidth(), viewportController_->GetViewport().GetCanvasHeight()); + viewportController_->FitContent(); + return true; + }; +#if ORTHANC_SANDBOXED == 0 + case 'c': + { + Scene2D& scene(viewportController_->GetScene()); + TakeScreenshot("screenshot.png", scene, viewportController_->GetViewport().GetCanvasWidth(), viewportController_->GetViewport().GetCanvasHeight()); + return true; + } +#endif + case 'd': + { + showCursorInfo_ = !showCursorInfo_; + if (!showCursorInfo_) + { + Scene2D& scene(viewportController_->GetScene()); + scene.DeleteLayer(BASIC_SCENE_LAYER_POSITION); + } + + return true; + } + } + } + return false; +} + +bool BasicScene2DInteractor::OnWheelEvent(const GuiAdapterWheelEvent& guiEvent) +{ + return false; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/MultiPlatform/BasicScene/BasicScene.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/MultiPlatform/BasicScene/BasicScene.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#pragma once + +#include +#include "Framework/Scene2DViewport/ViewportController.h" +#include "Framework/Scene2D/Scene2D.h" + +extern const unsigned int BASIC_SCENE_FONT_SIZE; +extern const int BASIC_SCENE_LAYER_POSITION; + +extern void PrepareScene(OrthancStone::Scene2D& scene); +extern void TakeScreenshot(const std::string& target, + const OrthancStone::Scene2D& scene, + unsigned int canvasWidth, + unsigned int canvasHeight); + + +#include "Applications/Generic/Scene2DInteractor.h" +#include "Framework/Scene2DViewport/IFlexiblePointerTracker.h" + + +class BasicScene2DInteractor : public OrthancStone::Scene2DInteractor +{ + boost::shared_ptr currentTracker_; + bool showCursorInfo_; +public: + BasicScene2DInteractor(boost::shared_ptr viewportController) : + Scene2DInteractor(viewportController), + showCursorInfo_(false) + {} + + virtual bool OnMouseEvent(const OrthancStone::GuiAdapterMouseEvent& event, const OrthancStone::PointerEvent& pointerEvent) override; + virtual bool OnKeyboardEvent(const OrthancStone::GuiAdapterKeyboardEvent& guiEvent) override; + virtual bool OnWheelEvent(const OrthancStone::GuiAdapterWheelEvent& guiEvent) override; +}; + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/MultiPlatform/BasicScene/mainQt.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/MultiPlatform/BasicScene/mainQt.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#define GLEW_STATIC 1 +// From Stone +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "../../Applications/Sdl/SdlWindow.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/ViewportController.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" + +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/Messages/MessageBroker.h" + +// From Orthanc framework +#include +#include +#include +#include +#include + +#include +#include +#include "EmbeddedResources.h" + +#include +#include +#include + +#include "BasicScene.h" + + +using namespace OrthancStone; + + + +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 ); + } +} + +extern void InitGL(); + +#include +#include "BasicSceneWindow.h" + +int main(int argc, char* argv[]) +{ + QApplication a(argc, argv); + + OrthancStone::Samples::BasicSceneWindow window; + window.show(); + window.GetOpenGlWidget().Init(); + + MessageBroker broker; + boost::shared_ptr undoStack(new UndoStack); + boost::shared_ptr controller = boost::make_shared(undoStack, boost::ref(broker), window.GetOpenGlWidget()); + PrepareScene(controller->GetScene()); + + window.GetOpenGlWidget().GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + BASIC_SCENE_FONT_SIZE, Orthanc::Encoding_Latin1); + + boost::shared_ptr interactor(new BasicScene2DInteractor(controller)); + window.GetOpenGlWidget().SetInteractor(interactor); + + controller->FitContent(); + + return a.exec(); +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/MultiPlatform/BasicScene/mainSdl.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/MultiPlatform/BasicScene/mainSdl.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,199 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +// From Stone +#include "Framework/Viewport/SdlViewport.h" +#include "Framework/Scene2D/OpenGLCompositor.h" +#include "Framework/Scene2DViewport/UndoStack.h" +#include "Framework/StoneInitialization.h" +#include "Framework/Messages/MessageBroker.h" + +// From Orthanc framework +#include +#include + +#include +#include + +#include +#include + + +#include "BasicScene.h" + +using namespace OrthancStone; + +boost::shared_ptr interactor; + +void HandleApplicationEvent(boost::shared_ptr controller, + const SDL_Event& event) +{ + using namespace OrthancStone; + Scene2D& scene(controller->GetScene()); + if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION) + { + // TODO: this code is copy/pasted from GuiAdapter::Run() -> find the right place + 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 guiEvent; + ConvertFromPlatform(guiEvent, ctrlPressed, shiftPressed, altPressed, event); + PointerEvent pointerEvent; + pointerEvent.AddPosition(controller->GetViewport().GetPixelCenterCoordinates(event.button.x, event.button.y)); + + interactor->OnMouseEvent(guiEvent, pointerEvent); + return; + } + else if ((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) && event.key.repeat == 0 /* Ignore key bounce */) + { + GuiAdapterKeyboardEvent guiEvent; + ConvertFromPlatform(guiEvent, event); + + interactor->OnKeyboardEvent(guiEvent); + } + +} + + +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 ); + } +} + + +void Run(boost::shared_ptr controller) +{ + SdlViewport& sdlViewport = dynamic_cast(controller->GetViewport()); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + controller->GetViewport().GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + BASIC_SCENE_FONT_SIZE, Orthanc::Encoding_Latin1); + + controller->GetViewport().Refresh(); + controller->FitContent(); + + + bool stop = false; + while (!stop) + { + controller->GetViewport().Refresh(); + + SDL_Event event; + while (!stop && + SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + stop = true; + break; + } + else if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + sdlViewport.UpdateSize(event.window.data1, event.window.data2); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + sdlViewport.GetWindow().ToggleMaximize(); + break; + + case SDLK_q: + stop = true; + break; + + default: + break; + } + } + + HandleApplicationEvent(controller, event); + } + + SDL_Delay(1); + } + interactor.reset(); +} + + + + +/** + * 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); + + try + { + SdlOpenGLViewport viewport("Hello", 1024, 768); + MessageBroker broker; + boost::shared_ptr undoStack(new UndoStack); + boost::shared_ptr controller = boost::make_shared(undoStack, boost::ref(broker), boost::ref(viewport)); + interactor.reset(new BasicScene2DInteractor(controller)); + PrepareScene(controller->GetScene()); + Run(controller); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Qt/BasicSceneWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Qt/BasicSceneWindow.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "BasicSceneWindow.h" + +/** + * Don't use "ui_MainWindow.h" instead of below, as + * this makes CMake unable to detect when the UI file changes. + **/ +#include +#include "../../Applications/Samples/SampleApplicationBase.h" + +namespace OrthancStone +{ + namespace Samples + { + + BasicSceneWindow::BasicSceneWindow( + QWidget *parent) : + ui_(new Ui::BasicSceneWindow) + { + ui_->setupUi(this); + } + + BasicSceneWindow::~BasicSceneWindow() + { + delete ui_; + } + + QStoneOpenGlWidget& BasicSceneWindow::GetOpenGlWidget() + { + return *(ui_->centralWidget); + } + + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Qt/BasicSceneWindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Qt/BasicSceneWindow.h Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ +#pragma once +#include +#include +// #include "../../Qt/QCairoWidget.h" +// #include "../../Qt/QStoneMainWindow.h" + +namespace Ui +{ + class BasicSceneWindow; +} + +namespace OrthancStone +{ + namespace Samples + { + + //class SampleSingleCanvasApplicationBase; + + class BasicSceneWindow : public QMainWindow + { + Q_OBJECT + + private: + Ui::BasicSceneWindow* ui_; + //SampleSingleCanvasApplicationBase& stoneSampleApplication_; + + public: + explicit BasicSceneWindow(QWidget *parent = 0); + ~BasicSceneWindow(); + + QStoneOpenGlWidget& GetOpenGlWidget(); + }; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Qt/BasicSceneWindow.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Qt/BasicSceneWindow.ui Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,84 @@ + + + BasicSceneWindow + + + + 0 + 0 + 903 + 634 + + + + + 500 + 300 + + + + + 500 + 300 + + + + Stone of Orthanc + + + Qt::LeftToRight + + + + + 0 + 0 + + + + Qt::LeftToRight + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 500 + + + + + + + + + + 0 + 0 + 903 + 21 + + + + + Test + + + + + + + + + QStoneOpenGlWidget + QWidget +
QStoneOpenGlWidget.h
+
+
+ + +
diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Qt/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Qt/CMakeLists.txt Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 2.8.3) + +##################################################################### +## Configuration of the Orthanc framework +##################################################################### + +# This CMake file defines the "ORTHANC_STONE_VERSION" macro, so it +# must be the first inclusion +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/Version.cmake) + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.5.7") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + + +##################################################################### +## Configuration of the Stone framework +##################################################################### + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +set(ORTHANC_STONE_APPLICATION_RESOURCES + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) +SET(ENABLE_QT ON) +SET(ENABLE_SDL OFF) +SET(ENABLE_WEB_CLIENT ON) +SET(ORTHANC_SANDBOXED OFF) +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) +##################################################################### +## Build the samples +##################################################################### + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +list(APPEND BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.cpp + ) + +ORTHANC_QT_WRAP_UI(BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.ui + ) + +ORTHANC_QT_WRAP_CPP(BASIC_SCENE_APPLICATIONS_SOURCES + BasicSceneWindow.h + QStoneOpenGlWidget.h + ) + +add_executable(MpBasicScene + ${CMAKE_CURRENT_LIST_DIR}/../MultiPlatform/BasicScene/BasicScene.h + ${CMAKE_CURRENT_LIST_DIR}/../MultiPlatform/BasicScene/BasicScene.cpp + ${CMAKE_CURRENT_LIST_DIR}/../MultiPlatform/BasicScene/mainQt.cpp + QStoneOpenGlWidget.cpp + ${BASIC_SCENE_APPLICATIONS_SOURCES} + ) + +target_include_directories(MpBasicScene PUBLIC ${CMAKE_SOURCE_DIR} ${ORTHANC_STONE_ROOT}) +target_link_libraries(MpBasicScene OrthancStone) diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Qt/QStoneOpenGlWidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Qt/QStoneOpenGlWidget.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,171 @@ +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include "QStoneOpenGlWidget.h" + +#include + +using namespace OrthancStone; + +void QStoneOpenGlWidget::initializeGL() +{ + glewInit(); +} + +void QStoneOpenGlWidget::MakeCurrent() +{ + this->makeCurrent(); +} + +void QStoneOpenGlWidget::resizeGL(int w, int h) +{ + +} + +void QStoneOpenGlWidget::paintGL() +{ + if (compositor_) + { + compositor_->Refresh(); + } + doneCurrent(); +} + +void ConvertFromPlatform( + OrthancStone::GuiAdapterMouseEvent& guiEvent, + PointerEvent& pointerEvent, + const QMouseEvent& qtEvent, + const IViewport& viewport) +{ + guiEvent.targetX = qtEvent.x(); + guiEvent.targetY = qtEvent.y(); + pointerEvent.AddPosition(viewport.GetPixelCenterCoordinates(guiEvent.targetX, guiEvent.targetY)); + + switch (qtEvent.button()) + { + case Qt::LeftButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_LEFT; break; + case Qt::MiddleButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_MIDDLE; break; + case Qt::RightButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_RIGHT; break; + default: + guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_LEFT; + } + + if (qtEvent.modifiers().testFlag(Qt::ShiftModifier)) + { + guiEvent.shiftKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::ControlModifier)) + { + guiEvent.ctrlKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::AltModifier)) + { + guiEvent.altKey = true; + } +} + +void QStoneOpenGlWidget::mouseEvent(QMouseEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType) +{ + OrthancStone::GuiAdapterMouseEvent guiEvent; + PointerEvent pointerEvent; + ConvertFromPlatform(guiEvent, pointerEvent, *qtEvent, *this); + guiEvent.type = guiEventType; + + if (sceneInteractor_.get() != NULL && compositor_.get() != NULL) + { + sceneInteractor_->OnMouseEvent(guiEvent, pointerEvent); + } + + // force redraw of the OpenGL widget + update(); +} + +void QStoneOpenGlWidget::mousePressEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEDOWN); +} + +void QStoneOpenGlWidget::mouseMoveEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEMOVE); +} + +void QStoneOpenGlWidget::mouseReleaseEvent(QMouseEvent* qtEvent) +{ + mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEUP); +} + +void ConvertFromPlatform( + OrthancStone::GuiAdapterKeyboardEvent& guiEvent, + const QKeyEvent& qtEvent) +{ + if (qtEvent.text().length() > 0) + { + guiEvent.sym[0] = qtEvent.text()[0].cell(); + } + else + { + guiEvent.sym[0] = 0; + } + guiEvent.sym[1] = 0; + + if (qtEvent.modifiers().testFlag(Qt::ShiftModifier)) + { + guiEvent.shiftKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::ControlModifier)) + { + guiEvent.ctrlKey = true; + } + if (qtEvent.modifiers().testFlag(Qt::AltModifier)) + { + guiEvent.altKey = true; + } + +} + + +bool QStoneOpenGlWidget::keyEvent(QKeyEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType) +{ + bool handled = false; + OrthancStone::GuiAdapterKeyboardEvent guiEvent; + ConvertFromPlatform(guiEvent, *qtEvent); + guiEvent.type = guiEventType; + + if (sceneInteractor_.get() != NULL && compositor_.get() != NULL) + { + handled = sceneInteractor_->OnKeyboardEvent(guiEvent); + + if (handled) + { + // force redraw of the OpenGL widget + update(); + } + } + return handled; +} + +void QStoneOpenGlWidget::keyPressEvent(QKeyEvent *qtEvent) +{ + bool handled = keyEvent(qtEvent, GUIADAPTER_EVENT_KEYDOWN); + if (!handled) + { + QOpenGLWidget::keyPressEvent(qtEvent); + } +} + +void QStoneOpenGlWidget::keyReleaseEvent(QKeyEvent *qtEvent) +{ + bool handled = keyEvent(qtEvent, GUIADAPTER_EVENT_KEYUP); + if (!handled) + { + QOpenGLWidget::keyPressEvent(qtEvent); + } +} + +void QStoneOpenGlWidget::wheelEvent(QWheelEvent *qtEvent) +{ + OrthancStone::GuiAdapterWheelEvent guiEvent; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + + // force redraw of the OpenGL widget + update(); +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Qt/QStoneOpenGlWidget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Qt/QStoneOpenGlWidget.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,90 @@ +#pragma once +#include "../../Framework/OpenGL/OpenGLIncludes.h" +#include +#include +#include + +#include +#include "../../Framework/OpenGL/IOpenGLContext.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Viewport/ViewportBase.h" +#include "../../Applications/Generic/Scene2DInteractor.h" + +namespace OrthancStone +{ + class QStoneOpenGlWidget : + public QOpenGLWidget, + public OpenGL::IOpenGLContext, + public ViewportBase + { + std::unique_ptr compositor_; + boost::shared_ptr sceneInteractor_; + QOpenGLContext openGlContext_; + + public: + QStoneOpenGlWidget(QWidget *parent) : + QOpenGLWidget(parent), + ViewportBase("QtStoneOpenGlWidget") // TODO: we shall be able to define a name but construction time is too early ! + { + setFocusPolicy(Qt::StrongFocus); // to enable keyPressEvent + setMouseTracking(true); // to enable mouseMoveEvent event when no button is pressed + } + + void Init() + { + QSurfaceFormat requestedFormat; + requestedFormat.setVersion( 2, 0 ); + openGlContext_.setFormat( requestedFormat ); + openGlContext_.create(); + openGlContext_.makeCurrent(context()->surface()); + + compositor_.reset(new OpenGLCompositor(*this, GetScene())); + } + + protected: + + //**** QWidget overrides + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void wheelEvent(QWheelEvent* event) override; + + //**** IOpenGLContext overrides + + virtual void MakeCurrent() override; + virtual void SwapBuffer() override {} + + virtual unsigned int GetCanvasWidth() const override + { + return this->width(); + } + + virtual unsigned int GetCanvasHeight() const override + { + return this->height(); + } + + public: + + void SetInteractor(boost::shared_ptr sceneInteractor) + { + sceneInteractor_ = sceneInteractor; + } + + virtual ICompositor& GetCompositor() + { + return *compositor_; + } + + protected: + void mouseEvent(QMouseEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType); + bool keyEvent(QKeyEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType); + + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Qt/Scene2DInteractor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Qt/Scene2DInteractor.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,72 @@ +#include "Scene2DInteractor.h" + +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" + + +namespace OrthancStone +{ + +} + +using namespace OrthancStone; + + +bool BasicScene2DInteractor::OnMouseEvent(const GuiAdapterMouseEvent& event, const PointerEvent& pointerEvent) +{ + if (currentTracker_.get() != NULL) + { + switch (event.type) + { + case GUIADAPTER_EVENT_MOUSEUP: + { + currentTracker_->PointerUp(pointerEvent); + if (!currentTracker_->IsAlive()) + { + currentTracker_.reset(); + } + };break; + case GUIADAPTER_EVENT_MOUSEMOVE: + { + currentTracker_->PointerMove(pointerEvent); + };break; + } + return true; + } + else + { + if (event.button == GUIADAPTER_MOUSEBUTTON_LEFT) + { + currentTracker_.reset(new RotateSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_MIDDLE) + { + currentTracker_.reset(new PanSceneTracker(viewportController_, pointerEvent)); + } + else if (event.button == GUIADAPTER_MOUSEBUTTON_RIGHT && compositor_.get() != NULL) + { + currentTracker_.reset(new ZoomSceneTracker(viewportController_, pointerEvent, compositor_->GetHeight())); + } + return true; + } + return false; +} + +bool BasicScene2DInteractor::OnKeyboardEvent(const GuiAdapterKeyboardEvent& guiEvent) +{ + switch (guiEvent.sym[0]) + { + case 's': + { + viewportController_->FitContent(compositor_->GetWidth(), compositor_->GetHeight()); + return true; + }; + } + return false; +} + +bool BasicScene2DInteractor::OnWheelEvent(const GuiAdapterWheelEvent& guiEvent) +{ + return false; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Qt/Scene2DInteractor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Qt/Scene2DInteractor.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,19 @@ +#pragma once + +#include "../../Applications/Generic/Scene2DInteractor.h" +#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" + + +class BasicScene2DInteractor : public OrthancStone::Scene2DInteractor +{ + boost::shared_ptr currentTracker_; +public: + BasicScene2DInteractor(boost::shared_ptr viewportController) : + Scene2DInteractor(viewportController) + {} + + virtual bool OnMouseEvent(const OrthancStone::GuiAdapterMouseEvent& event, const OrthancStone::PointerEvent& pointerEvent) override; + virtual bool OnKeyboardEvent(const OrthancStone::GuiAdapterKeyboardEvent& guiEvent); + virtual bool OnWheelEvent(const OrthancStone::GuiAdapterWheelEvent& guiEvent); +}; + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/README.md Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,195 @@ + +**These samples are deprecated and not guaranteed to work. A migration to a +new version of the Stone API is underway.** + +Please read orthanc-stone/Samples/README.md first for general requirements. + + + +Deprecated -- to be sorted +=========================== + +The following assumes that the source code to be downloaded in +`~/orthanc-stone` and Orthanc source code to be checked out in +`~/orthanc`. + +Building the WASM samples +------------------------------------- + +``` +cd ~/orthanc-stone/Applications/Samples +./build-wasm.sh +``` + +Serving the WASM samples +------------------------------------ +``` +# launch an Orthanc listening on 8042 port: +Orthanc + +# launch an nginx that will serve the WASM static files and reverse +# proxy +sudo nginx -p $(pwd) -c nginx.local.conf +``` + +You can now open the samples in http://localhost:9977 + +Building the SDL native samples (SimpleViewer only) +--------------------------------------------------- + +The following also assumes that you have checked out the Orthanc +source code in an `orthanc` folder next to the Stone of Orthanc +repository, please enter the following: + +**Simple make generator with dynamic build** + +``` +# Please set $currentDir to the current folder +mkdir -p ~/builds/orthanc-stone-build +cd ~/builds/orthanc-stone-build +cmake -DORTHANC_FRAMEWORK_SOURCE=path \ + -DORTHANC_FRAMEWORK_ROOT=$currentDir/../../../orthanc \ + -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON \ + ~/orthanc-stone/Applications/Samples/ +``` + +**Ninja generator with static SDL build (pwsh script)** + +``` +# Please yourself one level above the orthanc-stone and orthanc folders +if( -not (test-path stone_build_sdl)) { mkdir stone_build_sdl } +cd stone_build_sdl +cmake -G Ninja -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples/ +``` + +**Ninja generator with static SDL build (bash/zsh script)** + +``` +# Please yourself one level above the orthanc-stone and orthanc folders +if( -not (test-path stone_build_sdl)) { mkdir stone_build_sdl } +cd stone_build_sdl +cmake -G Ninja -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="`pwd`/../orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples/ +``` + +**Visual Studio 2017 generator with static SDL build (pwsh script)** + +``` +# The following will use Visual Studio 2017 to build the SDL samples +# in debug mode (with multiple compilers in parallel). NOTE: place +# yourself one level above the `orthanc-stone` and `orthanc` folders + +if( -not (test-path stone_build_sdl)) { mkdir stone_build_sdl } +cd stone_build_sdl +cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples/ +cmake --build . --config Debug +``` + +If you are working on Windows, add the correct generator option to +cmake to, for instance, generate msbuild files for Visual Studio. + +Then, under Linux: +``` +cmake --build . --target OrthancStoneSimpleViewer -- -j 5 +``` + +Note: replace `$($pwd)` with the current directory when not using Powershell + +Building the Qt native samples (SimpleViewer only) under Windows: +------------------------------------------------------------------ + +**Visual Studio 2017 generator with static Qt build (pwsh script)** + +For instance, if Qt is installed in `C:\Qt\5.12.0\msvc2017_64` + +``` +# The following will use Visual Studio 2017 to build the SDL samples +# in debug mode (with multiple compilers in parallel). NOTE: place +# yourself one level above the `orthanc-stone` and `orthanc` folders + +if( -not (test-path stone_build_qt)) { mkdir stone_build_qt } +cd stone_build_qt +cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DCMAKE_PREFIX_PATH=C:\Qt\5.12.0\msvc2017_64 -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_QT=ON ../orthanc-stone/Applications/Samples/ +cmake --build . --config Debug +``` + +Note: replace `$($pwd)` with the current directory when not using Powershell + + + + + + +Building the SDL native samples (SimpleViewer only) under Windows: +------------------------------------------------------------------ +`cmake -DSTATIC_BUILD=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON -G "Visual Studio 15 2017 Win64" ../orthanc-stone/Applications/Samples/` + +Note: replace `$($pwd)` with the current directory when not using Powershell + +Executing the native samples: +-------------------------------- +``` +# launch an Orthanc listening on 8042 port: +Orthanc + +# launch the sample +./OrthancStoneSimpleViewer --studyId=XX +``` + +Build the Application Samples +----------------------------- + +**Visual Studio 2008 (v90) ** + +``` +cmake -G "Visual Studio 9 2008" -DUSE_LEGACY_JSONCPP=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples +``` + +**Visual Studio 2019 (v142) ** + +``` +cmake -G "Visual Studio 16 2019" -A x64 -DMSVC_MULTIPLE_PROCESSES=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples +``` + +**Visual Studio 2017 (v140) ** + +``` +cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Applications/Samples +``` + + +Build the core Samples +--------------------------- +How to build the newest (2019-04-29) SDL samples under Windows, *inside* a +folder that is sibling to the orthanc-stone folder: + +**Visual Studio 2019 (v142) ** + +``` +cmake -G "Visual Studio 16 2019" -A x64 -DMSVC_MULTIPLE_PROCESSES=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl +``` + +**Visual Studio 2017 (v140) ** + +``` +cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl +``` + +**Visual Studio 2008 (v90) ** + +``` +cmake -G "Visual Studio 9 2008" -DUSE_LEGACY_JSONCPP=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl +``` + +And under Ubuntu (note the /mnt/c/osi/dev/orthanc folder): +``` +cmake -G "Ninja" -DENABLE_OPENGL=ON -DSTATIC_BUILD=OFF -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="/mnt/c/osi/dev/orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl +``` + +TODO trackers: +- CANCELLED (using outlined text now) text overlay 50% --> ColorTextureLayer 50% +- DONE angle tracker: draw arcs +- Handles on arc +- Select measure tool with hit test --> Delete command + + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Sdl/BasicScene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Sdl/BasicScene.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,418 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +// From Stone +#include "../../Framework/Viewport/SdlViewport.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/ZoomSceneTracker.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" + +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/Messages/MessageBroker.h" + +// From Orthanc framework +#include +#include +#include +#include +#include + +#include + +#include +#include + +static const unsigned int FONT_SIZE = 32; +static const int LAYER_POSITION = 150; + +#define OPENGL_ENABLED 0 + +void PrepareScene(OrthancStone::Scene2D& scene) +{ + using namespace OrthancStone; + + // Texture of 2x2 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast(i.GetRow(1)); + p[0] = 0; + p[1] = 0; + p[2] = 255; + + p[3] = 255; + p[4] = 0; + p[5] = 0; + + scene.SetLayer(12, new ColorTextureSceneLayer(i)); + + std::unique_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-3, 2); + l->SetPixelSpacing(1.5, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene.SetLayer(14, l.release()); + } + + // Texture of 1x1 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + std::unique_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-2, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene.SetLayer(13, l.release()); + } + + // Some lines + { + std::unique_ptr layer(new PolylineSceneLayer); + + 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, 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, 0, 255, 0); + + double dy = 1.01; + chain.clear(); + chain.push_back(ScenePoint2D(-4, -4)); + 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, 0, 0, 255); + + scene.SetLayer(50, layer.release()); + } + + // Some text + { + std::unique_ptr layer(new TextSceneLayer); + layer->SetText("Hello"); + scene.SetLayer(100, layer.release()); + } +} + + +void TakeScreenshot(const std::string& target, + 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); + 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); +} + + +void HandleApplicationEvent(const SDL_Event& event, + boost::shared_ptr& controller, + boost::shared_ptr& activeTracker) +{ + using namespace OrthancStone; + + Scene2D& scene = controller->GetScene(); + IViewport& viewport = controller->GetViewport(); + + if (event.type == SDL_MOUSEMOTION) + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + if (activeTracker.get() == NULL && + SDL_SCANCODE_LCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_LCTRL]) + { + // The "left-ctrl" key is down, while no tracker is present + + PointerEvent e; + e.AddPosition(viewport.GetPixelCenterCoordinates(event.button.x, event.button.y)); + + ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); + + char buf[64]; + sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY()); + + if (scene.HasLayer(LAYER_POSITION)) + { + TextSceneLayer& layer = + dynamic_cast(scene.GetLayer(LAYER_POSITION)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::unique_ptr + layer(new TextSceneLayer); + layer->SetColor(0, 255, 0); + layer->SetText(buf); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_BottomCenter); + layer->SetPosition(p.GetX(), p.GetY()); + scene.SetLayer(LAYER_POSITION, layer.release()); + } + } + else + { + scene.DeleteLayer(LAYER_POSITION); + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + PointerEvent e; + e.AddPosition(viewport.GetPixelCenterCoordinates(event.button.x, event.button.y)); + + switch (event.button.button) + { + case SDL_BUTTON_MIDDLE: + activeTracker = boost::make_shared(controller, e); + break; + + case SDL_BUTTON_RIGHT: + activeTracker = boost::make_shared + (controller, e, viewport.GetCanvasHeight()); + break; + + case SDL_BUTTON_LEFT: + activeTracker = boost::make_shared(controller, e); + break; + + default: + break; + } + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_s: + controller->FitContent(viewport.GetCanvasWidth(), + viewport.GetCanvasHeight()); + break; + + case SDLK_c: + TakeScreenshot("screenshot.png", scene, + viewport.GetCanvasWidth(), + viewport.GetCanvasHeight()); + break; + + default: + break; + } + } +} + +#if OPENGL_ENABLED==1 +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 + +void Run(OrthancStone::MessageBroker& broker, + OrthancStone::SdlViewport& viewport) +{ + using namespace OrthancStone; + + boost::shared_ptr controller( + new ViewportController(boost::make_shared(), broker, viewport)); + +#if OPENGL_ENABLED==1 + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); +#endif + + boost::shared_ptr tracker; + + bool firstShown = true; + bool stop = false; + while (!stop) + { + viewport.Refresh(); + + SDL_Event event; + while (!stop && + SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + stop = true; + break; + } + else if (event.type == SDL_MOUSEMOTION) + { + if (tracker) + { + PointerEvent e; + e.AddPosition(viewport.GetPixelCenterCoordinates( + event.button.x, event.button.y)); + tracker->PointerMove(e); + } + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + if (tracker) + { + PointerEvent e; + e.AddPosition(viewport.GetPixelCenterCoordinates( + event.button.x, event.button.y)); + tracker->PointerUp(e); + if(!tracker->IsAlive()) + tracker.reset(); + } + } + else if (event.type == SDL_WINDOWEVENT) + { + switch (event.window.event) + { + case SDL_WINDOWEVENT_SIZE_CHANGED: + tracker.reset(); + viewport.UpdateSize(event.window.data1, event.window.data2); + break; + + case SDL_WINDOWEVENT_SHOWN: + if (firstShown) + { + // Once the window is first shown, fit the content to its size + controller->FitContent(viewport.GetCanvasWidth(), viewport.GetCanvasHeight()); + firstShown = false; + } + + break; + + default: + break; + } + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + viewport.GetWindow().ToggleMaximize(); + break; + + case SDLK_q: + stop = true; + break; + + default: + break; + } + } + + HandleApplicationEvent(event, controller, tracker); + } + + SDL_Delay(1); + } +} + + + + +/** + * 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[]) +{ + OrthancStone::StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + + try + { +#if OPENGL_ENABLED==1 + OrthancStone::SdlOpenGLViewport viewport("Hello", 1024, 768); +#else + OrthancStone::SdlCairoViewport viewport("Hello", 1024, 768); +#endif + PrepareScene(viewport.GetScene()); + + viewport.GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE, Orthanc::Encoding_Latin1); + + OrthancStone::MessageBroker broker; + Run(broker, viewport); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + OrthancStone::StoneFinalize(); + + return 0; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Sdl/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Sdl/CMakeLists.txt Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,129 @@ +cmake_minimum_required(VERSION 2.8.3) + +##################################################################### +## Configuration of the Orthanc framework +##################################################################### + +# This CMake file defines the "ORTHANC_STONE_VERSION" macro, so it +# must be the first inclusion +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/Version.cmake) + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.5.7") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + + +##################################################################### +## Configuration of the Stone framework +##################################################################### + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +set(ORTHANC_STONE_APPLICATION_RESOURCES + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +SET(ENABLE_SDL_CONSOLE OFF CACHE BOOL "Enable the use of the MIT-licensed SDL_Console") +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) +SET(ENABLE_SDL ON) +SET(ENABLE_WEB_CLIENT ON) +SET(ORTHANC_SANDBOXED OFF) +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + + +##################################################################### +## Build the samples +##################################################################### + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +# +# BasicScene +# + +add_executable(BasicScene + BasicScene.cpp + ) + +target_link_libraries(BasicScene OrthancStone) + +# +# TrackerSample +# + +LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.cpp") +LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.h") + +if (MSVC AND MSVC_VERSION GREATER 1700) + LIST(APPEND TRACKERSAMPLE_SOURCE "cpp.hint") +endif() + +add_executable(TrackerSample + ${TRACKERSAMPLE_SOURCE} + ) + +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) + +# +# Multiplatform Basic Scene +# + +LIST(APPEND MP_BASIC_SCENE_SOURCE "../MultiPlatform/BasicScene/BasicScene.cpp") +LIST(APPEND MP_BASIC_SCENE_SOURCE "../MultiPlatform/BasicScene/BasicScene.h") +LIST(APPEND MP_BASIC_SCENE_SOURCE "../MultiPlatform/BasicScene/mainSdl.cpp") + +if (MSVC AND MSVC_VERSION GREATER 1700) + LIST(APPEND MP_BASIC_SCENE_SOURCE "cpp.hint") +endif() + +add_executable(MpBasicScene + ${MP_BASIC_SCENE_SOURCE} + ) + +target_include_directories(MpBasicScene PUBLIC ${ORTHANC_STONE_ROOT}) +target_link_libraries(MpBasicScene OrthancStone) diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Sdl/FusionMprSdl.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Sdl/FusionMprSdl.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,805 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "FusionMprSdl.h" + +#include "../../Framework/OpenGL/SdlOpenGLContext.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 +#include +#include +#include +#include + +#include +#include +#include + +#include +#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]; + } + + Scene2D& FusionMprSdlApp::GetScene() + { + return controller_->GetScene(); + } + + const Scene2D& FusionMprSdlApp::GetScene() const + { + return controller_->GetScene(); + } + + void FusionMprSdlApp::SelectNextTool() + { + currentTool_ = static_cast(currentTool_ + 1); + if (currentTool_ == FusionMprGuiTool_LAST) + currentTool_ = static_cast(0);; + printf("Current tool is now: %s\n", FusionMprMeasureToolToString(currentTool_)); + } + + void FusionMprSdlApp::DisplayInfoText() + { + // do not try to use stuff too early! + ICompositor* pCompositor = &(viewport_.GetCompositor()); + if (pCompositor == NULL) + return; + + std::stringstream msg; + + for (std::map::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( + GetScene().GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); + layerP = &layer; + } + else + { + std::unique_ptr 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 = viewport_.GetCompositor().GetCanvasWidth() * (-0.5); + double cY = viewport_.GetCompositor().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(GetScene().GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::unique_ptr 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(controller_->GetViewport().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(controller_->GetViewport().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(controller_->GetViewport().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(controller_->GetViewport().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(viewport_.GetCompositor().GetCanvasWidth(), + viewport_.GetCompositor().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", + viewport_.GetCompositor().GetCanvasWidth(), + viewport_.GetCompositor().GetCanvasHeight()); + break; + + default: + break; + } + } + } + + + void FusionMprSdlApp::OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message) + { + DisplayInfoText(); + } + + boost::shared_ptr FusionMprSdlApp::CreateSuitableTracker( + const SDL_Event & event, + const PointerEvent & e) + { + using namespace Orthanc; + + switch (event.button.button) + { + case SDL_BUTTON_MIDDLE: + return boost::shared_ptr(new PanSceneTracker + (controller_, e)); + + case SDL_BUTTON_RIGHT: + return boost::shared_ptr(new ZoomSceneTracker + (controller_, e, viewport_.GetCompositor().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 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(new RotateSceneTracker( + controller_, e)); + case FusionMprGuiTool_Pan: + return boost::shared_ptr(new PanSceneTracker( + controller_, e)); + case FusionMprGuiTool_Zoom: + return boost::shared_ptr(new ZoomSceneTracker( + controller_, e, viewport_.GetCompositor().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(new CreateLineMeasureTracker( + IObserver::GetBroker(), controller_, e)); + case FusionMprGuiTool_AngleMeasure: + return boost::shared_ptr(new CreateAngleMeasureTracker( + IObserver::GetBroker(), controller_, e)); + case FusionMprGuiTool_CircleMeasure: + LOG(ERROR) << "Not implemented yet!"; + return boost::shared_ptr(); + case FusionMprGuiTool_EllipseMeasure: + LOG(ERROR) << "Not implemented yet!"; + return boost::shared_ptr(); + default: + throw OrthancException(ErrorCode_InternalError, "Wrong tool!"); + } + } + } + default: + return boost::shared_ptr(); + } + } + + + FusionMprSdlApp::FusionMprSdlApp(MessageBroker& broker) + : IObserver(broker) + , broker_(broker) + , oracleObservable_(broker) + , oracle_(*this) + , currentTool_(FusionMprGuiTool_Rotate) + , undoStack_(new UndoStack) + , viewport_("Hello", 1024, 1024, false) // False means we do NOT let Windows treat this as a legacy application that needs to be scaled + { + //oracleObservable.RegisterObserverCallback + //(new Callable + // (*this, &FusionMprSdlApp::Handle)); + + //oracleObservable.RegisterObserverCallback + //(new Callable + // (*this, &FusionMprSdlApp::Handle)); + + //oracleObservable.RegisterObserverCallback + //(new Callable + // (*this, &ToFusionMprSdlAppto::Handle)); + + oracleObservable_.RegisterObserverCallback + (new Callable + (*this, &FusionMprSdlApp::Handle)); + + controller_ = boost::shared_ptr( + new ViewportController(undoStack_, broker_, viewport_)); + + controller_->RegisterObserverCallback( + new Callable + (*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(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast(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 FusionMprSdlApp::TrackerHitTest(const PointerEvent & e) + { + // std::vector> measureTools_; + return boost::shared_ptr(); + } + + 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 + (message.GetCommand()).GetUri().c_str()); + break; + + default: + break; + } + } + + void FusionMprSdlApp::SetVolume1(int depth, + const boost::shared_ptr& 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& 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& 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 + controller_->FitContent(viewport_.GetCanvasWidth(), viewport_.GetCanvasHeight()); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + viewport_.GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_0, Orthanc::Encoding_Latin1); + viewport_.GetCompositor().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 ct(new DicomVolumeImage); + boost::shared_ptr dose(new DicomVolumeImage); + + + boost::shared_ptr ctLoader; + boost::shared_ptr doseLoader; + boost::shared_ptr 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 + // (*this, &FusionMprSdlApp::Handle)); + ctLoader->RegisterObserverCallback + (new Callable + (*this, &FusionMprSdlApp::Handle)); + + this->SetVolume1(0, ctLoader, new GrayscaleStyleConfigurator); + + { + std::unique_ptr config(new LookupTableStyleConfigurator); + config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); + + boost::shared_ptr 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) + { + viewport_.GetCompositor().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); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + viewport_.GetWindow().ToggleMaximize(); + break; + + case SDLK_s: + controller_->FitContent(viewport_.GetCanvasWidth(), viewport_.GetCanvasHeight()); + break; + + case SDLK_q: + g_stopApplication = true; + break; + default: + break; + } + } + HandleApplicationEvent(event); + } + SDL_Delay(1); + } + + //// 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 g_app; + +void FusionMprSdl_SetInfoDisplayMessage(std::string key, std::string value) +{ + boost::shared_ptr 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 app(new FusionMprSdlApp(broker)); + g_app = app; + app->PrepareScene(); + app->Run(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Sdl/FusionMprSdl.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Sdl/FusionMprSdl.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,206 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "../../Framework/Viewport/SdlViewport.h" + +#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 +#include +#include + +#include + +namespace OrthancStone +{ + class OpenGLCompositor; + class IVolumeSlicer; + class ILayerStyleConfigurator; + class DicomStructureSetLoader; + class IOracle; + class ThreadedOracle; + class VolumeSceneLayerSource; + class NativeFusionMprApplicationContext; + class SdlOpenGLViewport; + + 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 + , public IMessageEmitter + { + public: + // 12 because. + FusionMprSdlApp(MessageBroker& broker); + + void PrepareScene(); + void Run(); + void SetInfoDisplayMessage(std::string key, std::string value); + void DisableTracker(); + + Scene2D& GetScene(); + 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 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 TrackerHitTest(const PointerEvent& e); + + boost::shared_ptr 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 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& volume, + ILayerStyleConfigurator* style); + + void SetVolume2( + int depth, + const boost::shared_ptr& volume, + ILayerStyleConfigurator* style); + + void SetStructureSet( + int depth, + const boost::shared_ptr& volume); + + + + private: + void DisplayFloatingCtrlInfoText(const PointerEvent& e); + void DisplayInfoText(); + void HideInfoText(); + + private: + CoordinateSystem3D plane_; + + boost::shared_ptr source1_, source2_, source3_; + + /** + 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 controller_; + + std::map infoTextMap_; + boost::shared_ptr 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_; + SdlOpenGLViewport viewport_; + }; + +} + + + \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Sdl/Loader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Sdl/Loader.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,518 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "../../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/Volumes/VolumeSceneLayerSource.h" +#include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h" +#include "../../Framework/Volumes/DicomVolumeImageReslicer.h" + +// From Orthanc framework +#include +#include +#include +#include +#include + + +namespace OrthancStone +{ + class NativeApplicationContext : public IMessageEmitter + { + private: + boost::shared_mutex mutex_; + MessageBroker broker_; + IObservable oracleObservable_; + + public: + NativeApplicationContext() : + oracleObservable_(broker_) + { + } + + + virtual void EmitMessage(const IObserver& observer, + const IMessage& message) ORTHANC_OVERRIDE + { + try + { + boost::unique_lock 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: + NativeApplicationContext& that_; + boost::shared_lock lock_; + + public: + ReaderLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + }; + + + class WriterLock : public boost::noncopyable + { + private: + NativeApplicationContext& that_; + boost::unique_lock lock_; + + public: + WriterLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + + MessageBroker& GetBroker() + { + return that_.broker_; + } + + IObservable& GetOracleObservable() + { + return that_.oracleObservable_; + } + }; + }; +} + + + +class Toto : public OrthancStone::IObserver +{ +private: + OrthancStone::CoordinateSystem3D plane_; + OrthancStone::IOracle& oracle_; + OrthancStone::Scene2D scene_; + std::unique_ptr 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& >(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); + + printf("ICI [%s]\n", v.toStyledString().c_str()); + } + + void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message) + { + printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); + } + + void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message) + { + printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); + } + + 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 OrthancStone::IOracleCommand::Type_GetOrthancWebViewerJpeg: + printf("URI: [%s]\n", dynamic_cast + (message.GetCommand()).GetUri().c_str()); + break; + + default: + break; + } + } + +public: + Toto(OrthancStone::IOracle& oracle, + OrthancStone::IObservable& oracleObservable) : + IObserver(oracleObservable.GetBroker()), + oracle_(oracle) + { + oracleObservable.RegisterObserverCallback + (new OrthancStone::Callable + (*this, &Toto::Handle)); + + oracleObservable.RegisterObserverCallback + (new OrthancStone::Callable + (*this, &Toto::Handle)); + + oracleObservable.RegisterObserverCallback + (new OrthancStone::Callable + (*this, &Toto::Handle)); + + oracleObservable.RegisterObserverCallback + (new OrthancStone::Callable + (*this, &Toto::Handle)); + + oracleObservable.RegisterObserverCallback + (new OrthancStone::Callable + (*this, &Toto::Handle)); + } + + void SetReferenceLoader(OrthancStone::IObservable& loader) + { + loader.RegisterObserverCallback + (new OrthancStone::Callable + (*this, &Toto::Handle)); + } + + void SetVolume1(int depth, + const boost::shared_ptr& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + source1_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); + + if (style != NULL) + { + source1_->SetConfigurator(style); + } + } + + void SetVolume2(int depth, + const boost::shared_ptr& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + source2_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); + + if (style != NULL) + { + source2_->SetConfigurator(style); + } + } + + void SetStructureSet(int depth, + const boost::shared_ptr& volume) + { + source3_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); + } + +}; + + +void Run(OrthancStone::NativeApplicationContext& context, + OrthancStone::ThreadedOracle& oracle) +{ + // the oracle has been supplied with the context (as an IEmitter) upon + // creation + boost::shared_ptr ct(new OrthancStone::DicomVolumeImage); + boost::shared_ptr dose(new OrthancStone::DicomVolumeImage); + + + boost::shared_ptr toto; + boost::shared_ptr ctLoader; + boost::shared_ptr doseLoader; + boost::shared_ptr rtstructLoader; + + { + 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())); + } + + + //toto->SetReferenceLoader(*ctLoader); + toto->SetReferenceLoader(*doseLoader); + + +#if 1 + toto->SetVolume1(0, ctLoader, new OrthancStone::GrayscaleStyleConfigurator); +#else + { + boost::shared_ptr reslicer(new OrthancStone::DicomVolumeImageReslicer(ct)); + toto->SetVolume1(0, reslicer, new OrthancStone::GrayscaleStyleConfigurator); + } +#endif + + + { + std::unique_ptr config(new OrthancStone::LookupTableStyleConfigurator); + config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); + + boost::shared_ptr 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::unique_ptr command(new OrthancStone::OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetUri("/tools/find"); + command->SetBody(v); + + oracle.Schedule(*toto, command.release()); + } + + if(0) + { + if (0) + { + std::unique_ptr 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::unique_ptr 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::unique_ptr 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::unique_ptr 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::unique_ptr 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::unique_ptr 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::unique_ptr command(new OrthancStone::SleepOracleCommand(i * 1000)); + command->SetPayload(new Orthanc::SingleValueObject(42 * i)); + 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 + + // Delphine + //ctLoader->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT + //ctLoader->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); // Lung 1/10mm + + + { + LOG(WARNING) << "...Waiting for Ctrl-C..."; + + oracle.Start(); + + 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(); + } +} + + + +/** + * 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[]) +{ + OrthancStone::StoneInitialize(); + //Orthanc::Logging::EnableInfoLevel(true); + + try + { + OrthancStone::NativeApplicationContext context; + + OrthancStone::ThreadedOracle oracle(context); + //oracle.SetThreadsCount(1); + + { + Orthanc::WebServiceParameters p; + //p.SetUrl("http://localhost:8043/"); + p.SetCredentials("orthanc", "orthanc"); + oracle.SetOrthancParameters(p); + } + + //oracle.Start(); + + Run(context, oracle); + + //oracle.Stop(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + OrthancStone::StoneFinalize(); + + return 0; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Sdl/RadiographyEditor.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Sdl/RadiographyEditor.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,267 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "../Shared/RadiographyEditorApp.h" + +// From Stone +#include "../../Framework/Oracle/SleepOracleCommand.h" +#include "../../Framework/Oracle/ThreadedOracle.h" +#include "../../Applications/Sdl/SdlOpenGLWindow.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/StoneInitialization.h" + +#include +#include + + +#include +#include + +#include +#include + +using namespace OrthancStone; + +namespace OrthancStone +{ + class NativeApplicationContext : public IMessageEmitter + { + private: + boost::shared_mutex mutex_; + MessageBroker broker_; + IObservable oracleObservable_; + + public: + NativeApplicationContext() : + oracleObservable_(broker_) + { + } + + + virtual void EmitMessage(const IObserver& observer, + const IMessage& message) ORTHANC_OVERRIDE + { + try + { + boost::unique_lock 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: + NativeApplicationContext& that_; + boost::shared_lock lock_; + + public: + ReaderLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + }; + + + class WriterLock : public boost::noncopyable + { + private: + NativeApplicationContext& that_; + boost::unique_lock lock_; + + public: + WriterLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + + MessageBroker& GetBroker() + { + return that_.broker_; + } + + IObservable& GetOracleObservable() + { + return that_.oracleObservable_; + } + }; + }; +} + +class OpenGlSdlCompositorFactory : public ICompositorFactory +{ + OpenGL::IOpenGLContext& openGlContext_; + +public: + OpenGlSdlCompositorFactory(OpenGL::IOpenGLContext& openGlContext) : + openGlContext_(openGlContext) + {} + + ICompositor* GetCompositor(const Scene2D& scene) + { + + OpenGLCompositor* compositor = new OpenGLCompositor(openGlContext_, scene); + 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); + return compositor; + } +}; + +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); + } +} + + +/** + * 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::NativeApplicationContext context; + OrthancStone::NativeApplicationContext::WriterLock lock(context); + OrthancStone::ThreadedOracle oracle(context); + + // False means we do NOT let Windows treat this as a legacy application + // that needs to be scaled + SdlOpenGLWindow window("Hello", 1024, 1024, false); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + std::unique_ptr compositorFactory(new OpenGlSdlCompositorFactory(window)); + boost::shared_ptr app(new RadiographyEditorApp(oracle, lock.GetOracleObservable(), compositorFactory.release())); + app->PrepareScene(); + app->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); + + bool stopApplication = false; + + while (!stopApplication) + { + app->Refresh(); + + SDL_Event event; + while (!stopApplication && SDL_PollEvent(&event)) + { + OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; + if (event.key.keysym.mod & KMOD_CTRL) + modifiers = static_cast(static_cast(modifiers) | static_cast(OrthancStone::KeyboardModifiers_Control)); + if (event.key.keysym.mod & KMOD_ALT) + modifiers = static_cast(static_cast(modifiers) | static_cast(OrthancStone::KeyboardModifiers_Alt)); + if (event.key.keysym.mod & KMOD_SHIFT) + modifiers = static_cast(static_cast(modifiers) | static_cast(OrthancStone::KeyboardModifiers_Shift)); + + OrthancStone::MouseButton button; + if (event.button.button == SDL_BUTTON_LEFT) + button = OrthancStone::MouseButton_Left; + else if (event.button.button == SDL_BUTTON_MIDDLE) + button = OrthancStone::MouseButton_Middle; + else if (event.button.button == SDL_BUTTON_RIGHT) + button = OrthancStone::MouseButton_Right; + + if (event.type == SDL_QUIT) + { + stopApplication = true; + break; + } + else if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + app->DisableTracker(); // was: tracker.reset(NULL); + app->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_q: + stopApplication = true; + break; + default: + { + app->OnKeyPressed(event.key.keysym.sym, modifiers); + } + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + app->OnMouseDown(event.button.x, event.button.y, modifiers, button); + } + else if (event.type == SDL_MOUSEMOTION) + { + app->OnMouseMove(event.button.x, event.button.y, modifiers); + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + app->OnMouseUp(event.button.x, event.button.y, modifiers, button); + } + } + SDL_Delay(1); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Sdl/TrackerSample.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Sdl/TrackerSample.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,93 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "TrackerSampleApp.h" + + // From Stone +#include "../../Framework/OpenGL/SdlOpenGLContext.h" +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/StoneInitialization.h" + +#include +#include + + +#include +#include + +#include +#include + +/* +TODO: + +- to decouple the trackers from the sample, we need to supply them with + the scene rather than the app + +- in order to do that, we need a GetNextFreeZIndex function (or something + along those lines) in the scene object + +*/ + +boost::weak_ptr g_app; + +void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value) +{ + boost::shared_ptr 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 + { + MessageBroker broker; + boost::shared_ptr app(new TrackerSampleApp(broker)); + g_app = app; + app->PrepareScene(); + app->Run(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Sdl/TrackerSampleApp.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Sdl/TrackerSampleApp.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,733 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "TrackerSampleApp.h" + +#include "../../Framework/OpenGL/SdlOpenGLContext.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 +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace OrthancStone +{ + const char* MeasureToolToString(size_t i) + { + static const char* descs[] = { + "GuiTool_Rotate", + "GuiTool_Pan", + "GuiTool_Zoom", + "GuiTool_LineMeasure", + "GuiTool_CircleMeasure", + "GuiTool_AngleMeasure", + "GuiTool_EllipseMeasure", + "GuiTool_LAST" + }; + if (i >= GuiTool_LAST) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong tool index"); + } + return descs[i]; + } + + void TrackerSampleApp::SelectNextTool() + { + currentTool_ = static_cast(currentTool_ + 1); + if (currentTool_ == GuiTool_LAST) + currentTool_ = static_cast(0);; + printf("Current tool is now: %s\n", MeasureToolToString(currentTool_)); + } + + void TrackerSampleApp::DisplayInfoText() + { + // do not try to use stuff too early! + std::stringstream msg; + + for (std::map::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 (controller_->GetScene().HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = dynamic_cast( + controller_->GetScene().GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); + layerP = &layer; + } + else + { + std::unique_ptr 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); + controller_->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 = GetCompositor().GetCanvasWidth() * (-0.5); + double cY = GetCompositor().GetCanvasHeight() * (-0.5); + controller_->GetScene().GetCanvasToSceneTransform().Apply(cX,cY); + layerP->SetPosition(cX, cY); + } + + void TrackerSampleApp::DisplayFloatingCtrlInfoText(const PointerEvent& e) + { + ScenePoint2D p = e.GetMainPosition().Apply(controller_->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 (controller_->GetScene().HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = + dynamic_cast(controller_->GetScene().GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::unique_ptr layer(new TextSceneLayer); + layer->SetColor(0, 255, 0); + layer->SetText(buf); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_BottomCenter); + layer->SetPosition(p.GetX(), p.GetY()); + controller_->GetScene().SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release()); + } + } + + void TrackerSampleApp::HideInfoText() + { + controller_->GetScene().DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX); + } + + ScenePoint2D TrackerSampleApp::GetRandomPointInScene() const + { + unsigned int w = GetCompositor().GetCanvasWidth(); + LOG(TRACE) << "GetCompositor().GetCanvasWidth() = " << + GetCompositor().GetCanvasWidth(); + unsigned int h = GetCompositor().GetCanvasHeight(); + LOG(TRACE) << "GetCompositor().GetCanvasHeight() = " << + GetCompositor().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 = controller_->GetViewport().GetPixelCenterCoordinates(x, y); + LOG(TRACE) << "--> p.GetX() = " << p.GetX() << " p.GetY() = " << p.GetY(); + + ScenePoint2D r = p.Apply(controller_->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 cmd = + boost::make_shared( + boost::ref(IObserver::GetBroker()), + controller_, + GetRandomPointInScene()); + cmd->SetEnd(GetRandomPointInScene()); + controller_->PushCommand(cmd); + } + break; + case 1: + // angle measure + { + boost::shared_ptr cmd = + boost::make_shared( + boost::ref(IObserver::GetBroker()), + controller_, + GetRandomPointInScene()); + cmd->SetCenter(GetRandomPointInScene()); + cmd->SetSide2End(GetRandomPointInScene()); + controller_->PushCommand(cmd); + } + break; + } + } + + void TrackerSampleApp::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(controller_->GetViewport().GetPixelCenterCoordinates( + event.button.x, event.button.y)); + + DisplayFloatingCtrlInfoText(e); + } + else if (activeTracker_.get() != NULL) + { + HideInfoText(); + //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)"; + if (activeTracker_.get() != NULL) + { + //LOG(TRACE) << "(activeTracker_.get() != NULL)"; + PointerEvent e; + e.AddPosition(controller_->GetViewport().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 + { + HideInfoText(); + + PointerEvent e; + e.AddPosition(controller_->GetViewport().GetPixelCenterCoordinates(event.button.x, event.button.y)); + + ScenePoint2D scenePos = e.GetMainPosition().Apply( + controller_->GetScene().GetCanvasToSceneTransform()); + //auto measureTools = GetController()->HitTestMeasureTools(scenePos); + //LOG(TRACE) << "# of hit tests: " << measureTools.size(); + + // this returns the collection of measuring tools where hit test is true + std::vector > measureTools = controller_->HitTestMeasureTools(scenePos); + + // let's refresh the measuring tools highlighted state + // first let's tag them as "unhighlighted" + controller_->ResetMeasuringToolsHighlight(); + + // then immediately take the first one and ask it to highlight the + // measuring tool UI part that is hot + if (measureTools.size() > 0) + { + measureTools[0]->Highlight(scenePos); + } + } + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + if (activeTracker_) + { + PointerEvent e; + e.AddPosition(controller_->GetViewport().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(controller_->GetViewport().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_m: + CreateRandomMeasureTool(); + break; + case SDLK_s: + controller_->FitContent(GetCompositor().GetCanvasWidth(), + GetCompositor().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", + GetCompositor().GetCanvasWidth(), + GetCompositor().GetCanvasHeight()); + break; + + default: + break; + } + } + } + + + void TrackerSampleApp::OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message) + { + DisplayInfoText(); + } + + boost::shared_ptr TrackerSampleApp::CreateSuitableTracker( + const SDL_Event & event, + const PointerEvent & e) + { + using namespace Orthanc; + + switch (event.button.button) + { + case SDL_BUTTON_MIDDLE: + return boost::shared_ptr(new PanSceneTracker + (controller_, e)); + + case SDL_BUTTON_RIGHT: + return boost::shared_ptr(new ZoomSceneTracker + (controller_, e, GetCompositor().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 hitTestTracker = TrackerHitTest(e); + + if (hitTestTracker != NULL) + { + //LOG(TRACE) << "hitTestTracker != NULL"; + return hitTestTracker; + } + else + { + switch (currentTool_) + { + case GuiTool_Rotate: + //LOG(TRACE) << "Creating RotateSceneTracker"; + return boost::shared_ptr(new RotateSceneTracker( + controller_, e)); + case GuiTool_Pan: + return boost::shared_ptr(new PanSceneTracker( + controller_, e)); + case GuiTool_Zoom: + return boost::shared_ptr(new ZoomSceneTracker( + controller_, e, GetCompositor().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 GuiTool_LineMeasure: + return boost::shared_ptr(new CreateLineMeasureTracker( + IObserver::GetBroker(), controller_, e)); + case GuiTool_AngleMeasure: + return boost::shared_ptr(new CreateAngleMeasureTracker( + IObserver::GetBroker(), controller_, e)); + case GuiTool_CircleMeasure: + LOG(ERROR) << "Not implemented yet!"; + return boost::shared_ptr(); + case GuiTool_EllipseMeasure: + LOG(ERROR) << "Not implemented yet!"; + return boost::shared_ptr(); + default: + throw OrthancException(ErrorCode_InternalError, "Wrong tool!"); + } + } + } + default: + return boost::shared_ptr(); + } + } + + + TrackerSampleApp::TrackerSampleApp(MessageBroker& broker) : IObserver(broker) + , currentTool_(GuiTool_Rotate) + , undoStack_(new UndoStack) + , viewport_("Hello", 1024, 1024, false) // False means we do NOT let Windows treat this as a legacy application that needs to be scaled + { + controller_ = boost::shared_ptr( + new ViewportController(undoStack_, broker, viewport_)); + + controller_->RegisterObserverCallback( + new Callable + (*this, &TrackerSampleApp::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 TrackerSampleApp::PrepareScene() + { + // Texture of 2x2 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t* p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast(i.GetRow(1)); + p[0] = 0; + p[1] = 0; + p[2] = 255; + + p[3] = 255; + p[4] = 0; + p[5] = 0; + + controller_->GetScene().SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i)); + + std::unique_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-3, 2); + l->SetPixelSpacing(1.5, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + controller_->GetScene().SetLayer(TEXTURE_2x2_2_ZINDEX, l.release()); + } + + // Texture of 1x1 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); + + uint8_t* p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + std::unique_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-2, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + controller_->GetScene().SetLayer(TEXTURE_1x1_ZINDEX, l.release()); + } + + // Some lines + { + std::unique_ptr layer(new PolylineSceneLayer); + + layer->SetThickness(1); + + 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, 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, 0, 255, 0); + + double dy = 1.01; + chain.clear(); + chain.push_back(ScenePoint2D(-4, -4)); + 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, 0, 0, 255); + + controller_->GetScene().SetLayer(LINESET_1_ZINDEX, layer.release()); + } + + // Some text + { + std::unique_ptr layer(new TextSceneLayer); + layer->SetText("Hello"); + controller_->GetScene().SetLayer(LINESET_2_ZINDEX, layer.release()); + } + } + + + void TrackerSampleApp::DisableTracker() + { + if (activeTracker_) + { + activeTracker_->Cancel(); + activeTracker_.reset(); + } + } + + void TrackerSampleApp::TakeScreenshot(const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight) + { + CairoCompositor compositor(controller_->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 TrackerSampleApp::TrackerHitTest(const PointerEvent & e) + { + // std::vector> measureTools_; + ScenePoint2D scenePos = e.GetMainPosition().Apply( + controller_->GetScene().GetCanvasToSceneTransform()); + + std::vector > measureTools = controller_->HitTestMeasureTools(scenePos); + + if (measureTools.size() > 0) + { + return measureTools[0]->CreateEditionTracker(e); + } + return boost::shared_ptr(); + } + + 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; + + ICompositor& TrackerSampleApp::GetCompositor() + { + using namespace Orthanc; + try + { + SdlViewport& viewport = dynamic_cast(viewport_); + return viewport.GetCompositor(); + } + catch (std::bad_cast e) + { + throw OrthancException(ErrorCode_InternalError, "Wrong viewport type!"); + } + } + + const ICompositor& TrackerSampleApp::GetCompositor() const + { + using namespace Orthanc; + try + { + SdlViewport& viewport = const_cast(dynamic_cast(viewport_)); + return viewport.GetCompositor(); + } + catch (std::bad_cast e) + { + throw OrthancException(ErrorCode_InternalError, "Wrong viewport type!"); + } + } + + + void TrackerSampleApp::Run() + { + controller_->FitContent(viewport_.GetCanvasWidth(), viewport_.GetCanvasHeight()); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_0, Orthanc::Encoding_Latin1); + GetCompositor().SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_1, Orthanc::Encoding_Latin1); + + while (!g_stopApplication) + { + GetCompositor().Refresh(); + + 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); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + viewport_.GetWindow().ToggleMaximize(); + break; + + case SDLK_q: + g_stopApplication = true; + break; + default: + break; + } + } + HandleApplicationEvent(event); + } + SDL_Delay(1); + } + } + + void TrackerSampleApp::SetInfoDisplayMessage( + std::string key, std::string value) + { + if (value == "") + infoTextMap_.erase(key); + else + infoTextMap_[key] = value; + DisplayInfoText(); + } + +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Sdl/TrackerSampleApp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Sdl/TrackerSampleApp.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,148 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "../../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 "../../Framework/Viewport/SdlViewport.h" + +#include + +#include +#include +#include + +namespace OrthancStone +{ + enum GuiTool + { + GuiTool_Rotate = 0, + GuiTool_Pan, + GuiTool_Zoom, + GuiTool_LineMeasure, + GuiTool_CircleMeasure, + GuiTool_AngleMeasure, + GuiTool_EllipseMeasure, + GuiTool_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; + + class TrackerSampleApp : public IObserver + , public boost::enable_shared_from_this + { + public: + // 12 because. + TrackerSampleApp(MessageBroker& broker); + void PrepareScene(); + void Run(); + void SetInfoDisplayMessage(std::string key, std::string value); + void DisableTracker(); + + 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); + + private: + void SelectNextTool(); + void CreateRandomMeasureTool(); + + + /** + In the case of this app, the viewport is an SDL viewport and it has + a OpenGLCompositor& GetCompositor() method + */ + ICompositor& GetCompositor(); + + /** + See the other overload + */ + const ICompositor& GetCompositor() const; + + /** + This returns a random point in the canvas part of the scene, but in + scene coordinates + */ + ScenePoint2D GetRandomPointInScene() const; + + boost::shared_ptr TrackerHitTest(const PointerEvent& e); + + boost::shared_ptr 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 cmd); + void Undo(); + void Redo(); + + private: + void DisplayFloatingCtrlInfoText(const PointerEvent& e); + void DisplayInfoText(); + void HideInfoText(); + + private: + /** + 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 controller_; + + std::map infoTextMap_; + boost::shared_ptr 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; + + GuiTool currentTool_; + boost::shared_ptr undoStack_; + SdlOpenGLViewport viewport_; + }; + +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/Sdl/cpp.hint --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/Sdl/cpp.hint Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,2 @@ +#define ORTHANC_OVERRIDE +#define ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(FILE, LINE, NAME) class NAME : public ::OrthancStone::IMessage {}; diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/BasicMPR.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/BasicMPR.cpp Wed Apr 22 14:05:47 2020 +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-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + + +#include "dev.h" + +#include + +#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::unique_ptr source_; + VolumeProjection projection_; + std::vector 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& 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 + (*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(currentPlane_) + delta; + unsigned int next; + + if (tmp < 0) + { + next = 0; + } + else if (tmp >= static_cast(planes_.size())) + { + next = planes_.size() - 1; + } + else + { + next = static_cast(tmp); + } + + if (next != currentPlane_) + { + currentPlane_ = next; + Refresh(); + } + } + } + }; +} + + + + +boost::shared_ptr ct_(new OrthancStone::DicomVolumeImage); + +boost::shared_ptr loader_; + +std::unique_ptr widget1_; +std::unique_ptr widget2_; +std::unique_ptr 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(userData); + + if (ctrlDown_) + { + delta *= static_cast(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 + (*this, &TestSleep::Handle)); + + LOG(INFO) << "STARTING"; + Schedule(); + } + }; + + //static TestSleep testSleep(broker_, oracle_); +} + + + +static std::map arguments_; + +static bool GetArgument(std::string& value, + const std::string& key) +{ + std::map::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::unique_ptr 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::unique_ptr 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::unique_ptr 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(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnWindowResize); // DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 !! + + 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(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyDown); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_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(); + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/BasicMPR.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/BasicMPR.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,60 @@ + + + + + + + + + + + + Stone of Orthanc + + + + + + + + + + + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/BasicScene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/BasicScene.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,210 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "dev.h" + +#include +#include + +// From Stone +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/StoneInitialization.h" + +// From Orthanc framework +#include +#include +#include + +void PrepareScene(OrthancStone::Scene2D& scene) +{ + using namespace OrthancStone; + + // Texture of 2x2 size + if (1) + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast(i.GetRow(1)); + p[0] = 0; + p[1] = 0; + p[2] = 255; + + p[3] = 255; + p[4] = 0; + p[5] = 0; + + scene.SetLayer(12, new ColorTextureSceneLayer(i)); + + std::unique_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-3, 2); + l->SetPixelSpacing(1.5, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene.SetLayer(14, l.release()); + } + + // Texture of 1x1 size + if (1) + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); + + uint8_t *p = reinterpret_cast(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + std::unique_ptr l(new ColorTextureSceneLayer(i)); + l->SetOrigin(-2, 1); + l->SetAngle(20.0 / 180.0 * M_PI); + scene.SetLayer(13, l.release()); + } + + // Some lines + if (1) + { + std::unique_ptr layer(new PolylineSceneLayer); + + layer->SetThickness(1); + + 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, 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, 0, 255, 0); + + double dy = 1.01; + chain.clear(); + chain.push_back(ScenePoint2D(-4, -4)); + 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, 0, 0, 255); + + scene.SetLayer(50, layer.release()); + } + + // Some text + if (1) + { + std::unique_ptr layer(new TextSceneLayer); + layer->SetText("Hello"); + scene.SetLayer(100, layer.release()); + } +} + + +std::unique_ptr viewport1_; +std::unique_ptr viewport2_; +std::unique_ptr viewport3_; +boost::shared_ptr controller1_; +boost::shared_ptr controller2_; +boost::shared_ptr controller3_; +OrthancStone::MessageBroker broker_; + + +EM_BOOL OnWindowResize( + int eventType, const EmscriptenUiEvent *uiEvent, void *userData) +{ + if (viewport1_.get() != NULL) + { + viewport1_->UpdateSize(); + } + + if (viewport2_.get() != NULL) + { + viewport2_->UpdateSize(); + } + + if (viewport3_.get() != NULL) + { + viewport3_->UpdateSize(); + } + + 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("mycanvas1")); + PrepareScene(viewport1_->GetScene()); + viewport1_->UpdateSize(); + + viewport2_.reset(new OrthancStone::WebAssemblyViewport("mycanvas2")); + PrepareScene(viewport2_->GetScene()); + viewport2_->UpdateSize(); + + viewport3_.reset(new OrthancStone::WebAssemblyViewport("mycanvas3")); + PrepareScene(viewport3_->GetScene()); + viewport3_->UpdateSize(); + + viewport1_->GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE, Orthanc::Encoding_Latin1); + viewport2_->GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE, Orthanc::Encoding_Latin1); + viewport3_->GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE, Orthanc::Encoding_Latin1); + + controller1_.reset(new OrthancStone::ViewportController(boost::make_shared(), broker_, *viewport1_)); + controller2_.reset(new OrthancStone::ViewportController(boost::make_shared(), broker_, *viewport2_)); + controller3_.reset(new OrthancStone::ViewportController(boost::make_shared(), broker_, *viewport3_)); + + controller1_->FitContent(viewport1_->GetCanvasWidth(), viewport1_->GetCanvasHeight()); + controller2_->FitContent(viewport2_->GetCanvasWidth(), viewport2_->GetCanvasHeight()); + controller3_->FitContent(viewport3_->GetCanvasWidth(), viewport3_->GetCanvasHeight()); + + viewport1_->Refresh(); + viewport2_->Refresh(); + viewport3_->Refresh(); + + SetupEvents("mycanvas1", controller1_); + SetupEvents("mycanvas2", controller2_); + SetupEvents("mycanvas3", controller3_); + + emscripten_set_resize_callback("#window", NULL, false, OnWindowResize); + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/BasicScene.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/BasicScene.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,69 @@ + + + + + + + + + + + + Stone of Orthanc + + + + + + + + + + + + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/CMakeLists.txt Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,119 @@ +cmake_minimum_required(VERSION 2.8.3) + + +##################################################################### +## Configuration of the Emscripten compiler for WebAssembly target +##################################################################### + +set(WASM_FLAGS "-s WASM=1 -s FETCH=1") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +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") + +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") + + +##################################################################### +## Configuration of the Orthanc framework +##################################################################### + +# This CMake file defines the "ORTHANC_STONE_VERSION" macro, so it +# must be the first inclusion +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/Version.cmake) + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.5.7") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + + +##################################################################### +## Configuration of the Stone framework +##################################################################### + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) +include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +set(ORTHANC_STONE_APPLICATION_RESOURCES + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) +SET(ORTHANC_SANDBOXED ON) +SET(ENABLE_WASM ON) + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + + +##################################################################### +## Build the samples +##################################################################### + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + + +if (ON) + add_executable(BasicScene + BasicScene.cpp + #${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.h + ${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.cpp + ) + + target_link_libraries(BasicScene OrthancStone) + + install( + TARGETS BasicScene + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} + ) +endif() + + +if (ON) + add_executable(BasicMPR + BasicMPR.cpp + ) + + 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} + ) diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/Configuration.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/Configuration.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,17 @@ +{ + "Plugins": [ + "/usr/local/share/orthanc/plugins/libOrthancWebViewer.so", + "/usr/local/share/orthanc/plugins/libServeFolders.so" + ], + "StorageDirectory" : "/var/lib/orthanc/db", + "IndexDirectory" : "/var/lib/orthanc/db", + "RemoteAccessAllowed" : true, + "AuthenticationEnabled" : false, + "ServeFolders" : { + "AllowCache" : false, + "GenerateETag" : true, + "Folders" : { + "/stone" : "/root/stone" + } + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/ConfigurationLocalSJO.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/ConfigurationLocalSJO.json Wed Apr 22 14:05:47 2020 +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" + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/NOTES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/NOTES.txt Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,76 @@ +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 +$ 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 + + +Local AM +======== + +. ~/apps/emsdk/emsdk_env.sh +cd /mnt/c/o/ +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=${EMSDK}/fastcomp/emscripten/cmake/Modules/Platform/Emscripten.cmake -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=/mnt/c/o/orthanc/ -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON /mnt/c/o/orthanc-stone/Samples/WebAssembly -DCMAKE_INSTALL_PREFIX=/mnt/c/o/build_install_stone_newsamples_wasm_wsl +ninja diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/app.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/app.js Wed Apr 22 14:05:47 2020 +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); + }); +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/dev.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/dev.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,202 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../../Framework/Viewport/WebAssemblyViewport.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2DViewport/UndoStack.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" + +#include + +#include +#include + +static const unsigned int FONT_SIZE = 32; + +namespace OrthancStone +{ + class ActiveTracker : public boost::noncopyable + { + private: + boost::shared_ptr tracker_; + std::string canvasIdentifier_; + bool insideCanvas_; + + public: + ActiveTracker(const boost::shared_ptr& tracker, + const std::string& canvasId) : + tracker_(tracker), + canvasIdentifier_(canvasId), + 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::IViewport& viewport) +{ + std::unique_ptr 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::unique_ptr tracker_; + +EM_BOOL OnMouseEvent(int eventType, + const EmscriptenMouseEvent *mouseEvent, + void *userData) +{ + if (mouseEvent != NULL && + userData != NULL) + { + boost::shared_ptr& controller = + *reinterpret_cast*>(userData); + + switch (eventType) + { + case EMSCRIPTEN_EVENT_CLICK: + { + static unsigned int count = 0; + char buf[64]; + sprintf(buf, "click %d", count++); + + std::unique_ptr layer(new OrthancStone::TextSceneLayer); + layer->SetText(buf); + controller->GetViewport().GetScene().SetLayer(100, layer.release()); + controller->GetViewport().Refresh(); + break; + } + + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + boost::shared_ptr t; + + { + std::unique_ptr event( + ConvertMouseEvent(*mouseEvent, controller->GetViewport())); + + switch (mouseEvent->button) + { + case 0: // Left button + emscripten_console_log("Creating RotateSceneTracker"); + t.reset(new OrthancStone::RotateSceneTracker( + controller, *event)); + break; + + case 1: // Middle button + emscripten_console_log("Creating PanSceneTracker"); + LOG(INFO) << "Creating PanSceneTracker" ; + t.reset(new OrthancStone::PanSceneTracker( + controller, *event)); + break; + + case 2: // Right button + emscripten_console_log("Creating ZoomSceneTracker"); + t.reset(new OrthancStone::ZoomSceneTracker( + controller, *event, controller->GetViewport().GetCanvasWidth())); + break; + + default: + break; + } + } + + if (t.get() != NULL) + { + tracker_.reset( + new OrthancStone::ActiveTracker(t, controller->GetViewport().GetCanvasIdentifier())); + controller->GetViewport().Refresh(); + } + + break; + } + + case EMSCRIPTEN_EVENT_MOUSEMOVE: + if (tracker_.get() != NULL) + { + std::unique_ptr event( + ConvertMouseEvent(*mouseEvent, controller->GetViewport())); + tracker_->PointerMove(*event); + controller->GetViewport().Refresh(); + } + break; + + case EMSCRIPTEN_EVENT_MOUSEUP: + if (tracker_.get() != NULL) + { + std::unique_ptr event( + ConvertMouseEvent(*mouseEvent, controller->GetViewport())); + tracker_->PointerUp(*event); + controller->GetViewport().Refresh(); + if (!tracker_->IsAlive()) + tracker_.reset(); + } + break; + + default: + break; + } + } + + return true; +} + + +void SetupEvents(const std::string& canvas, + boost::shared_ptr& controller) +{ + emscripten_set_mousedown_callback(canvas.c_str(), &controller, false, OnMouseEvent); + emscripten_set_mousemove_callback(canvas.c_str(), &controller, false, OnMouseEvent); + emscripten_set_mouseup_callback(canvas.c_str(), &controller, false, OnMouseEvent); +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Deprecated/WebAssembly/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Deprecated/WebAssembly/index.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,16 @@ + + + + + + + Stone of Orthanc + + +

Available samples

+ + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/MultiPlatform/BasicScene/BasicScene.cpp --- a/Samples/MultiPlatform/BasicScene/BasicScene.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,275 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "BasicScene.h" - -// From Stone -#include "Framework/Scene2D/Scene2D.h" -#include "Framework/Scene2D/ColorTextureSceneLayer.h" -#include "Framework/Scene2D/PolylineSceneLayer.h" -#include "Framework/Scene2D/TextSceneLayer.h" - -#include "Framework/Scene2D/PanSceneTracker.h" -#include "Framework/Scene2D/ZoomSceneTracker.h" -#include "Framework/Scene2D/RotateSceneTracker.h" - -#include "Framework/Scene2D/CairoCompositor.h" - -// From Orthanc framework -#include -#include -#include - -using namespace OrthancStone; - -const unsigned int BASIC_SCENE_FONT_SIZE = 32; -const int BASIC_SCENE_LAYER_POSITION = 150; - -void PrepareScene(Scene2D& scene) -{ - //Scene2D& scene(*controller->GetScene()); - // Texture of 2x2 size - { - Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); - - uint8_t *p = reinterpret_cast(i.GetRow(0)); - p[0] = 255; - p[1] = 0; - p[2] = 0; - - p[3] = 0; - p[4] = 255; - p[5] = 0; - - p = reinterpret_cast(i.GetRow(1)); - p[0] = 0; - p[1] = 0; - p[2] = 255; - - p[3] = 255; - p[4] = 0; - p[5] = 0; - - scene.SetLayer(12, new ColorTextureSceneLayer(i)); - - std::unique_ptr l(new ColorTextureSceneLayer(i)); - l->SetOrigin(-3, 2); - l->SetPixelSpacing(1.5, 1); - l->SetAngle(20.0 / 180.0 * 3.14); - scene.SetLayer(14, l.release()); - } - - // Texture of 1x1 size - { - Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); - - uint8_t *p = reinterpret_cast(i.GetRow(0)); - p[0] = 255; - p[1] = 0; - p[2] = 0; - - std::unique_ptr l(new ColorTextureSceneLayer(i)); - l->SetOrigin(-2, 1); - l->SetAngle(20.0 / 180.0 * 3.14); - scene.SetLayer(13, l.release()); - } - - // Some lines - { - std::unique_ptr layer(new PolylineSceneLayer); - - layer->SetThickness(1); - - 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, 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, 0, 255, 0); - - double dy = 1.01; - chain.clear(); - chain.push_back(ScenePoint2D(-4, -4)); - 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, 0, 0, 255); - - // layer->SetColor(0,255, 255); - scene.SetLayer(50, layer.release()); - } - - // Some text - { - std::unique_ptr layer(new TextSceneLayer); - layer->SetText("Hello"); - scene.SetLayer(100, layer.release()); - } -} - -#if ORTHANC_SANDBOXED == 0 -void TakeScreenshot(const std::string& target, - 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, BASIC_SCENE_FONT_SIZE, 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); -} -#endif - -void ShowCursorInfo(Scene2D& scene, const PointerEvent& pointerEvent) -{ - ScenePoint2D p = pointerEvent.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); - - char buf[64]; - sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY()); - - if (scene.HasLayer(BASIC_SCENE_LAYER_POSITION)) - { - TextSceneLayer& layer = - dynamic_cast(scene.GetLayer(BASIC_SCENE_LAYER_POSITION)); - layer.SetText(buf); - layer.SetPosition(p.GetX(), p.GetY()); - } - else - { - std::unique_ptr - layer(new TextSceneLayer); - layer->SetColor(0, 255, 0); - layer->SetText(buf); - layer->SetBorder(20); - layer->SetAnchor(BitmapAnchor_BottomCenter); - layer->SetPosition(p.GetX(), p.GetY()); - scene.SetLayer(BASIC_SCENE_LAYER_POSITION, layer.release()); - } -} - - - -bool BasicScene2DInteractor::OnMouseEvent(const GuiAdapterMouseEvent& event, const PointerEvent& pointerEvent) -{ - if (currentTracker_.get() != NULL) - { - switch (event.type) - { - case GUIADAPTER_EVENT_MOUSEUP: - { - currentTracker_->PointerUp(pointerEvent); - if (!currentTracker_->IsAlive()) - { - currentTracker_.reset(); - } - };break; - case GUIADAPTER_EVENT_MOUSEMOVE: - { - currentTracker_->PointerMove(pointerEvent); - };break; - default: - return false; - } - return true; - } - else if (event.type == GUIADAPTER_EVENT_MOUSEDOWN) - { - if (event.button == GUIADAPTER_MOUSEBUTTON_LEFT) - { - currentTracker_.reset(new RotateSceneTracker(viewportController_, pointerEvent)); - } - else if (event.button == GUIADAPTER_MOUSEBUTTON_MIDDLE) - { - currentTracker_.reset(new PanSceneTracker(viewportController_, pointerEvent)); - } - else if (event.button == GUIADAPTER_MOUSEBUTTON_RIGHT) - { - currentTracker_.reset(new ZoomSceneTracker(viewportController_, pointerEvent, viewportController_->GetViewport().GetCanvasHeight())); - } - } - else if (event.type == GUIADAPTER_EVENT_MOUSEMOVE) - { - if (showCursorInfo_) - { - Scene2D& scene(viewportController_->GetScene()); - ShowCursorInfo(scene, pointerEvent); - } - return true; - } - return false; -} - -bool BasicScene2DInteractor::OnKeyboardEvent(const GuiAdapterKeyboardEvent& guiEvent) -{ - if (guiEvent.type == GUIADAPTER_EVENT_KEYDOWN) - { - switch (guiEvent.sym[0]) - { - case 's': - { - //viewportController_->FitContent(viewportController_->GetViewport().GetCanvasWidth(), viewportController_->GetViewport().GetCanvasHeight()); - viewportController_->FitContent(); - return true; - }; -#if ORTHANC_SANDBOXED == 0 - case 'c': - { - Scene2D& scene(viewportController_->GetScene()); - TakeScreenshot("screenshot.png", scene, viewportController_->GetViewport().GetCanvasWidth(), viewportController_->GetViewport().GetCanvasHeight()); - return true; - } -#endif - case 'd': - { - showCursorInfo_ = !showCursorInfo_; - if (!showCursorInfo_) - { - Scene2D& scene(viewportController_->GetScene()); - scene.DeleteLayer(BASIC_SCENE_LAYER_POSITION); - } - - return true; - } - } - } - return false; -} - -bool BasicScene2DInteractor::OnWheelEvent(const GuiAdapterWheelEvent& guiEvent) -{ - return false; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/MultiPlatform/BasicScene/BasicScene.h --- a/Samples/MultiPlatform/BasicScene/BasicScene.h Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#include -#include "Framework/Scene2DViewport/ViewportController.h" -#include "Framework/Scene2D/Scene2D.h" - -extern const unsigned int BASIC_SCENE_FONT_SIZE; -extern const int BASIC_SCENE_LAYER_POSITION; - -extern void PrepareScene(OrthancStone::Scene2D& scene); -extern void TakeScreenshot(const std::string& target, - const OrthancStone::Scene2D& scene, - unsigned int canvasWidth, - unsigned int canvasHeight); - - -#include "Applications/Generic/Scene2DInteractor.h" -#include "Framework/Scene2DViewport/IFlexiblePointerTracker.h" - - -class BasicScene2DInteractor : public OrthancStone::Scene2DInteractor -{ - boost::shared_ptr currentTracker_; - bool showCursorInfo_; -public: - BasicScene2DInteractor(boost::shared_ptr viewportController) : - Scene2DInteractor(viewportController), - showCursorInfo_(false) - {} - - virtual bool OnMouseEvent(const OrthancStone::GuiAdapterMouseEvent& event, const OrthancStone::PointerEvent& pointerEvent) override; - virtual bool OnKeyboardEvent(const OrthancStone::GuiAdapterKeyboardEvent& guiEvent) override; - virtual bool OnWheelEvent(const OrthancStone::GuiAdapterWheelEvent& guiEvent) override; -}; - diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/MultiPlatform/BasicScene/mainQt.cpp --- a/Samples/MultiPlatform/BasicScene/mainQt.cpp Mon Apr 20 18:26:32 2020 +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-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#define GLEW_STATIC 1 -// From Stone -#include "../../Framework/OpenGL/OpenGLIncludes.h" -#include "../../Applications/Sdl/SdlWindow.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/ViewportController.h" -#include "../../Framework/Scene2DViewport/UndoStack.h" - -#include "../../Framework/StoneInitialization.h" -#include "../../Framework/Messages/MessageBroker.h" - -// From Orthanc framework -#include -#include -#include -#include -#include - -#include -#include -#include "EmbeddedResources.h" - -#include -#include -#include - -#include "BasicScene.h" - - -using namespace OrthancStone; - - - -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 ); - } -} - -extern void InitGL(); - -#include -#include "BasicSceneWindow.h" - -int main(int argc, char* argv[]) -{ - QApplication a(argc, argv); - - OrthancStone::Samples::BasicSceneWindow window; - window.show(); - window.GetOpenGlWidget().Init(); - - MessageBroker broker; - boost::shared_ptr undoStack(new UndoStack); - boost::shared_ptr controller = boost::make_shared(undoStack, boost::ref(broker), window.GetOpenGlWidget()); - PrepareScene(controller->GetScene()); - - window.GetOpenGlWidget().GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, - BASIC_SCENE_FONT_SIZE, Orthanc::Encoding_Latin1); - - boost::shared_ptr interactor(new BasicScene2DInteractor(controller)); - window.GetOpenGlWidget().SetInteractor(interactor); - - controller->FitContent(); - - return a.exec(); -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/MultiPlatform/BasicScene/mainSdl.cpp --- a/Samples/MultiPlatform/BasicScene/mainSdl.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,199 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -// From Stone -#include "Framework/Viewport/SdlViewport.h" -#include "Framework/Scene2D/OpenGLCompositor.h" -#include "Framework/Scene2DViewport/UndoStack.h" -#include "Framework/StoneInitialization.h" -#include "Framework/Messages/MessageBroker.h" - -// From Orthanc framework -#include -#include - -#include -#include - -#include -#include - - -#include "BasicScene.h" - -using namespace OrthancStone; - -boost::shared_ptr interactor; - -void HandleApplicationEvent(boost::shared_ptr controller, - const SDL_Event& event) -{ - using namespace OrthancStone; - Scene2D& scene(controller->GetScene()); - if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION) - { - // TODO: this code is copy/pasted from GuiAdapter::Run() -> find the right place - 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 guiEvent; - ConvertFromPlatform(guiEvent, ctrlPressed, shiftPressed, altPressed, event); - PointerEvent pointerEvent; - pointerEvent.AddPosition(controller->GetViewport().GetPixelCenterCoordinates(event.button.x, event.button.y)); - - interactor->OnMouseEvent(guiEvent, pointerEvent); - return; - } - else if ((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) && event.key.repeat == 0 /* Ignore key bounce */) - { - GuiAdapterKeyboardEvent guiEvent; - ConvertFromPlatform(guiEvent, event); - - interactor->OnKeyboardEvent(guiEvent); - } - -} - - -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 ); - } -} - - -void Run(boost::shared_ptr controller) -{ - SdlViewport& sdlViewport = dynamic_cast(controller->GetViewport()); - - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(OpenGLMessageCallback, 0); - - controller->GetViewport().GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, - BASIC_SCENE_FONT_SIZE, Orthanc::Encoding_Latin1); - - controller->GetViewport().Refresh(); - controller->FitContent(); - - - bool stop = false; - while (!stop) - { - controller->GetViewport().Refresh(); - - SDL_Event event; - while (!stop && - SDL_PollEvent(&event)) - { - if (event.type == SDL_QUIT) - { - stop = true; - break; - } - else if (event.type == SDL_WINDOWEVENT && - event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) - { - sdlViewport.UpdateSize(event.window.data1, event.window.data2); - } - else if (event.type == SDL_KEYDOWN && - event.key.repeat == 0 /* Ignore key bounce */) - { - switch (event.key.keysym.sym) - { - case SDLK_f: - sdlViewport.GetWindow().ToggleMaximize(); - break; - - case SDLK_q: - stop = true; - break; - - default: - break; - } - } - - HandleApplicationEvent(controller, event); - } - - SDL_Delay(1); - } - interactor.reset(); -} - - - - -/** - * 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); - - try - { - SdlOpenGLViewport viewport("Hello", 1024, 768); - MessageBroker broker; - boost::shared_ptr undoStack(new UndoStack); - boost::shared_ptr controller = boost::make_shared(undoStack, boost::ref(broker), boost::ref(viewport)); - interactor.reset(new BasicScene2DInteractor(controller)); - PrepareScene(controller->GetScene()); - Run(controller); - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "EXCEPTION: " << e.What(); - } - - StoneFinalize(); - - return 0; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Qt/BasicSceneWindow.cpp --- a/Samples/Qt/BasicSceneWindow.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ -#include "../../Framework/OpenGL/OpenGLIncludes.h" -#include "BasicSceneWindow.h" - -/** - * Don't use "ui_MainWindow.h" instead of below, as - * this makes CMake unable to detect when the UI file changes. - **/ -#include -#include "../../Applications/Samples/SampleApplicationBase.h" - -namespace OrthancStone -{ - namespace Samples - { - - BasicSceneWindow::BasicSceneWindow( - QWidget *parent) : - ui_(new Ui::BasicSceneWindow) - { - ui_->setupUi(this); - } - - BasicSceneWindow::~BasicSceneWindow() - { - delete ui_; - } - - QStoneOpenGlWidget& BasicSceneWindow::GetOpenGlWidget() - { - return *(ui_->centralWidget); - } - - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Qt/BasicSceneWindow.h --- a/Samples/Qt/BasicSceneWindow.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ -#pragma once -#include -#include -// #include "../../Qt/QCairoWidget.h" -// #include "../../Qt/QStoneMainWindow.h" - -namespace Ui -{ - class BasicSceneWindow; -} - -namespace OrthancStone -{ - namespace Samples - { - - //class SampleSingleCanvasApplicationBase; - - class BasicSceneWindow : public QMainWindow - { - Q_OBJECT - - private: - Ui::BasicSceneWindow* ui_; - //SampleSingleCanvasApplicationBase& stoneSampleApplication_; - - public: - explicit BasicSceneWindow(QWidget *parent = 0); - ~BasicSceneWindow(); - - QStoneOpenGlWidget& GetOpenGlWidget(); - }; - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Qt/BasicSceneWindow.ui --- a/Samples/Qt/BasicSceneWindow.ui Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ - - - BasicSceneWindow - - - - 0 - 0 - 903 - 634 - - - - - 500 - 300 - - - - - 500 - 300 - - - - Stone of Orthanc - - - Qt::LeftToRight - - - - - 0 - 0 - - - - Qt::LeftToRight - - - - QLayout::SetDefaultConstraint - - - - - - 0 - 500 - - - - - - - - - - 0 - 0 - 903 - 21 - - - - - Test - - - - - - - - - QStoneOpenGlWidget - QWidget -
QStoneOpenGlWidget.h
-
-
- - -
diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Qt/CMakeLists.txt --- a/Samples/Qt/CMakeLists.txt Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) - -##################################################################### -## Configuration of the Orthanc framework -##################################################################### - -# This CMake file defines the "ORTHANC_STONE_VERSION" macro, so it -# must be the first inclusion -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/Version.cmake) - -if (ORTHANC_STONE_VERSION STREQUAL "mainline") - set(ORTHANC_FRAMEWORK_VERSION "mainline") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") -else() - set(ORTHANC_FRAMEWORK_VERSION "1.5.7") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") -endif() - -set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") -set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") -set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") - - -##################################################################### -## Configuration of the Stone framework -##################################################################### - -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) - -DownloadPackage( - "a24b8136b8f3bb93f166baf97d9328de" - "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" - "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") - -set(ORTHANC_STONE_APPLICATION_RESOURCES - UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf - ) - -SET(ENABLE_GOOGLE_TEST OFF) -SET(ENABLE_LOCALE ON) -SET(ENABLE_QT ON) -SET(ENABLE_SDL OFF) -SET(ENABLE_WEB_CLIENT ON) -SET(ORTHANC_SANDBOXED OFF) -LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) - -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) - -add_definitions( - -DORTHANC_ENABLE_LOGGING_PLUGIN=0 - ) -##################################################################### -## Build the samples -##################################################################### - -add_library(OrthancStone STATIC - ${ORTHANC_STONE_SOURCES} - ) - -list(APPEND BASIC_SCENE_APPLICATIONS_SOURCES - BasicSceneWindow.cpp - ) - -ORTHANC_QT_WRAP_UI(BASIC_SCENE_APPLICATIONS_SOURCES - BasicSceneWindow.ui - ) - -ORTHANC_QT_WRAP_CPP(BASIC_SCENE_APPLICATIONS_SOURCES - BasicSceneWindow.h - QStoneOpenGlWidget.h - ) - -add_executable(MpBasicScene - ${CMAKE_CURRENT_LIST_DIR}/../MultiPlatform/BasicScene/BasicScene.h - ${CMAKE_CURRENT_LIST_DIR}/../MultiPlatform/BasicScene/BasicScene.cpp - ${CMAKE_CURRENT_LIST_DIR}/../MultiPlatform/BasicScene/mainQt.cpp - QStoneOpenGlWidget.cpp - ${BASIC_SCENE_APPLICATIONS_SOURCES} - ) - -target_include_directories(MpBasicScene PUBLIC ${CMAKE_SOURCE_DIR} ${ORTHANC_STONE_ROOT}) -target_link_libraries(MpBasicScene OrthancStone) diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Qt/QStoneOpenGlWidget.cpp --- a/Samples/Qt/QStoneOpenGlWidget.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,171 +0,0 @@ -#include "../../Framework/OpenGL/OpenGLIncludes.h" -#include "QStoneOpenGlWidget.h" - -#include - -using namespace OrthancStone; - -void QStoneOpenGlWidget::initializeGL() -{ - glewInit(); -} - -void QStoneOpenGlWidget::MakeCurrent() -{ - this->makeCurrent(); -} - -void QStoneOpenGlWidget::resizeGL(int w, int h) -{ - -} - -void QStoneOpenGlWidget::paintGL() -{ - if (compositor_) - { - compositor_->Refresh(); - } - doneCurrent(); -} - -void ConvertFromPlatform( - OrthancStone::GuiAdapterMouseEvent& guiEvent, - PointerEvent& pointerEvent, - const QMouseEvent& qtEvent, - const IViewport& viewport) -{ - guiEvent.targetX = qtEvent.x(); - guiEvent.targetY = qtEvent.y(); - pointerEvent.AddPosition(viewport.GetPixelCenterCoordinates(guiEvent.targetX, guiEvent.targetY)); - - switch (qtEvent.button()) - { - case Qt::LeftButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_LEFT; break; - case Qt::MiddleButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_MIDDLE; break; - case Qt::RightButton: guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_RIGHT; break; - default: - guiEvent.button = OrthancStone::GUIADAPTER_MOUSEBUTTON_LEFT; - } - - if (qtEvent.modifiers().testFlag(Qt::ShiftModifier)) - { - guiEvent.shiftKey = true; - } - if (qtEvent.modifiers().testFlag(Qt::ControlModifier)) - { - guiEvent.ctrlKey = true; - } - if (qtEvent.modifiers().testFlag(Qt::AltModifier)) - { - guiEvent.altKey = true; - } -} - -void QStoneOpenGlWidget::mouseEvent(QMouseEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType) -{ - OrthancStone::GuiAdapterMouseEvent guiEvent; - PointerEvent pointerEvent; - ConvertFromPlatform(guiEvent, pointerEvent, *qtEvent, *this); - guiEvent.type = guiEventType; - - if (sceneInteractor_.get() != NULL && compositor_.get() != NULL) - { - sceneInteractor_->OnMouseEvent(guiEvent, pointerEvent); - } - - // force redraw of the OpenGL widget - update(); -} - -void QStoneOpenGlWidget::mousePressEvent(QMouseEvent* qtEvent) -{ - mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEDOWN); -} - -void QStoneOpenGlWidget::mouseMoveEvent(QMouseEvent* qtEvent) -{ - mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEMOVE); -} - -void QStoneOpenGlWidget::mouseReleaseEvent(QMouseEvent* qtEvent) -{ - mouseEvent(qtEvent, GUIADAPTER_EVENT_MOUSEUP); -} - -void ConvertFromPlatform( - OrthancStone::GuiAdapterKeyboardEvent& guiEvent, - const QKeyEvent& qtEvent) -{ - if (qtEvent.text().length() > 0) - { - guiEvent.sym[0] = qtEvent.text()[0].cell(); - } - else - { - guiEvent.sym[0] = 0; - } - guiEvent.sym[1] = 0; - - if (qtEvent.modifiers().testFlag(Qt::ShiftModifier)) - { - guiEvent.shiftKey = true; - } - if (qtEvent.modifiers().testFlag(Qt::ControlModifier)) - { - guiEvent.ctrlKey = true; - } - if (qtEvent.modifiers().testFlag(Qt::AltModifier)) - { - guiEvent.altKey = true; - } - -} - - -bool QStoneOpenGlWidget::keyEvent(QKeyEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType) -{ - bool handled = false; - OrthancStone::GuiAdapterKeyboardEvent guiEvent; - ConvertFromPlatform(guiEvent, *qtEvent); - guiEvent.type = guiEventType; - - if (sceneInteractor_.get() != NULL && compositor_.get() != NULL) - { - handled = sceneInteractor_->OnKeyboardEvent(guiEvent); - - if (handled) - { - // force redraw of the OpenGL widget - update(); - } - } - return handled; -} - -void QStoneOpenGlWidget::keyPressEvent(QKeyEvent *qtEvent) -{ - bool handled = keyEvent(qtEvent, GUIADAPTER_EVENT_KEYDOWN); - if (!handled) - { - QOpenGLWidget::keyPressEvent(qtEvent); - } -} - -void QStoneOpenGlWidget::keyReleaseEvent(QKeyEvent *qtEvent) -{ - bool handled = keyEvent(qtEvent, GUIADAPTER_EVENT_KEYUP); - if (!handled) - { - QOpenGLWidget::keyPressEvent(qtEvent); - } -} - -void QStoneOpenGlWidget::wheelEvent(QWheelEvent *qtEvent) -{ - OrthancStone::GuiAdapterWheelEvent guiEvent; - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - - // force redraw of the OpenGL widget - update(); -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Qt/QStoneOpenGlWidget.h --- a/Samples/Qt/QStoneOpenGlWidget.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -#pragma once -#include "../../Framework/OpenGL/OpenGLIncludes.h" -#include -#include -#include - -#include -#include "../../Framework/OpenGL/IOpenGLContext.h" -#include "../../Framework/Scene2D/OpenGLCompositor.h" -#include "../../Framework/Viewport/ViewportBase.h" -#include "../../Applications/Generic/Scene2DInteractor.h" - -namespace OrthancStone -{ - class QStoneOpenGlWidget : - public QOpenGLWidget, - public OpenGL::IOpenGLContext, - public ViewportBase - { - std::unique_ptr compositor_; - boost::shared_ptr sceneInteractor_; - QOpenGLContext openGlContext_; - - public: - QStoneOpenGlWidget(QWidget *parent) : - QOpenGLWidget(parent), - ViewportBase("QtStoneOpenGlWidget") // TODO: we shall be able to define a name but construction time is too early ! - { - setFocusPolicy(Qt::StrongFocus); // to enable keyPressEvent - setMouseTracking(true); // to enable mouseMoveEvent event when no button is pressed - } - - void Init() - { - QSurfaceFormat requestedFormat; - requestedFormat.setVersion( 2, 0 ); - openGlContext_.setFormat( requestedFormat ); - openGlContext_.create(); - openGlContext_.makeCurrent(context()->surface()); - - compositor_.reset(new OpenGLCompositor(*this, GetScene())); - } - - protected: - - //**** QWidget overrides - void initializeGL() override; - void resizeGL(int w, int h) override; - void paintGL() override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent *event) override; - void wheelEvent(QWheelEvent* event) override; - - //**** IOpenGLContext overrides - - virtual void MakeCurrent() override; - virtual void SwapBuffer() override {} - - virtual unsigned int GetCanvasWidth() const override - { - return this->width(); - } - - virtual unsigned int GetCanvasHeight() const override - { - return this->height(); - } - - public: - - void SetInteractor(boost::shared_ptr sceneInteractor) - { - sceneInteractor_ = sceneInteractor; - } - - virtual ICompositor& GetCompositor() - { - return *compositor_; - } - - protected: - void mouseEvent(QMouseEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType); - bool keyEvent(QKeyEvent* qtEvent, OrthancStone::GuiAdapterHidEventType guiEventType); - - }; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Qt/Scene2DInteractor.cpp --- a/Samples/Qt/Scene2DInteractor.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -#include "Scene2DInteractor.h" - -#include "../../Framework/Scene2D/PanSceneTracker.h" -#include "../../Framework/Scene2D/ZoomSceneTracker.h" -#include "../../Framework/Scene2D/RotateSceneTracker.h" - - -namespace OrthancStone -{ - -} - -using namespace OrthancStone; - - -bool BasicScene2DInteractor::OnMouseEvent(const GuiAdapterMouseEvent& event, const PointerEvent& pointerEvent) -{ - if (currentTracker_.get() != NULL) - { - switch (event.type) - { - case GUIADAPTER_EVENT_MOUSEUP: - { - currentTracker_->PointerUp(pointerEvent); - if (!currentTracker_->IsAlive()) - { - currentTracker_.reset(); - } - };break; - case GUIADAPTER_EVENT_MOUSEMOVE: - { - currentTracker_->PointerMove(pointerEvent); - };break; - } - return true; - } - else - { - if (event.button == GUIADAPTER_MOUSEBUTTON_LEFT) - { - currentTracker_.reset(new RotateSceneTracker(viewportController_, pointerEvent)); - } - else if (event.button == GUIADAPTER_MOUSEBUTTON_MIDDLE) - { - currentTracker_.reset(new PanSceneTracker(viewportController_, pointerEvent)); - } - else if (event.button == GUIADAPTER_MOUSEBUTTON_RIGHT && compositor_.get() != NULL) - { - currentTracker_.reset(new ZoomSceneTracker(viewportController_, pointerEvent, compositor_->GetHeight())); - } - return true; - } - return false; -} - -bool BasicScene2DInteractor::OnKeyboardEvent(const GuiAdapterKeyboardEvent& guiEvent) -{ - switch (guiEvent.sym[0]) - { - case 's': - { - viewportController_->FitContent(compositor_->GetWidth(), compositor_->GetHeight()); - return true; - }; - } - return false; -} - -bool BasicScene2DInteractor::OnWheelEvent(const GuiAdapterWheelEvent& guiEvent) -{ - return false; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Qt/Scene2DInteractor.h --- a/Samples/Qt/Scene2DInteractor.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -#pragma once - -#include "../../Applications/Generic/Scene2DInteractor.h" -#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" - - -class BasicScene2DInteractor : public OrthancStone::Scene2DInteractor -{ - boost::shared_ptr currentTracker_; -public: - BasicScene2DInteractor(boost::shared_ptr viewportController) : - Scene2DInteractor(viewportController) - {} - - virtual bool OnMouseEvent(const OrthancStone::GuiAdapterMouseEvent& event, const OrthancStone::PointerEvent& pointerEvent) override; - virtual bool OnKeyboardEvent(const OrthancStone::GuiAdapterKeyboardEvent& guiEvent); - virtual bool OnWheelEvent(const OrthancStone::GuiAdapterWheelEvent& guiEvent); -}; - diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/README.md Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,183 @@ +General +======= +These samples assume that a recent version of Orthanc is checked out in an +`orthanc` folder next to the `orthanc-stone` folder. Let's call the top folder +the `devroot` folder. This name does not matter and is not used anywhere. + +Here's the directory layout that we suggest: + +``` +devroot/ + | + +- orthanc/ + | + +- orthanc-stone/ + | + ... +``` + + Orthanc can be retrieved with: + ``` + hg clone https://hg.orthanc-server.com/orthanc + ``` + +Furthermore, the samples usually assume that an Orthanc is running locally, +without authentication, on port 8042. The samples can easily be tweaked if +your setup is different. + +When Dicom resources are to be displayed, their IDs can be supplied in the +various ways suitable for the platform (command-line arguments, URL parameters +or through the GUI) + + +WebAssembly samples +=================== + +Building the WebAssembly samples require the Emscripten SDK +(https://emscripten.org/). This SDK goes far beyond the simple compilation to +the wasm (Web Assembly) bytecode and provides a comprehensive library that +eases porting native C and C++ programs and libraries. The Emscripten SDK also +makes it easy to generate the companion Javascript files requires to use a +wasm module in a web application. + +Although Emscripten runs on all major platforms, Stone of Orthanc is developed +and tested with the Linux version of Emscripten. + +Emscripten runs perfectly fine under the Windows Subsystem for Linux (that is +the environment used quite often by the Stone of Orthanc team) + +**Important note:** The following examples **and the build scripts** will +assume that you have installed the Emscripten SDK in `~/apps/emsdk`. + +The following packages should get you going (a Debian-like distribution such +as Debian or Ubuntu is assumed) + +``` +sudo apt-get update +sudo apt-get install -y build-essential curl wget git python cmake pkg-config +sudo apt-get install -y mercurial unzip npm ninja-build p7zip-full gettext-base +``` + +SingleFrameViewer +----------------- + +This sample application displays a single frame of a Dicom instance that can +be loaded from Orthanc, either by using the Orthanc REST API or through the +Dicomweb server functionality of Orthanc. + +This barebones sample uses plain Javascript and requires the +Emscripten toolchain and cmake, in addition to a few standard packages. + +Here's how you can build it: create the following script (for instance, +`build-wasm-SingleFrameViewer.sh`) one level above the orthanc-stone repository, +thus, in the folder we have called `devroot`. + +If you feel confident, you can also simply read the following script and +enter the commands interactively in the terminal. + +``` +#!/bin/bash + +if [ ! -d "orthanc-stone" ]; then + echo "This script must be run from the folder one level above orthanc-stone" + exit 1 +fi + +if [[ ! $# -eq 1 ]]; then + echo "Usage:" + echo " $0 [BUILD_TYPE]" + echo "" + echo " with:" + echo " BUILD_TYPE = Debug, RelWithDebInfo or Release" + echo "" + exit 1 +fi + +# define the variables that we'll use +buildType=$1 +buildFolderName="`pwd`/out/build-stone-wasm-SingleFrameViewer-$buildType" +installFolderName="`pwd`/out/install-stone-wasm-SingleFrameViewer-$buildType" + +# configure the environment to use Emscripten +. ~/apps/emsdk/emsdk_env.sh + + +mkdir -p $buildFolderName + +# change current folder to the build folder +pushd $buildFolderName + +emcmake cmake -G "Ninja" \ + -DCMAKE_BUILD_TYPE=$buildType \ + -DCMAKE_INSTALL_PREFIX=$installFolderName \ + -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DALLOW_DOWNLOADS=ON \ + ../orthanc-stone/Samples/WebAssembly/SingleFrameViewer + +# perform build + installation +ninja install + +# restore the original working folder +popd + +echo "If all went well, the output files can be found in $installFolderName:" + +ls $installFolderName``` +``` + +Simply navigate to the dev root, and execute the script: + +``` +./build-wasm-SingleFrameViewer.sh RelWithDebInfo +``` + +I suggest that you do *not* use the `Debug` confirmation unless you really +need it, for the additional checks that are made will lead to a very long +build time and much slower execution (more severe than with a native non-wasm +target) + +Native samples +================= + +SdlSimpleViewer +--------------- + +### Windows build + +Here's how to build the SdlSimpleViewer example using Visual Studio 2019 +(the shell is Powershell, but the legacy shell can also be used with some +tweaks). This example is meant to be launched from the folder above +orthanc-stone. + +``` + # create the build folder and navigate to it + $buildDir = "build-stone-sdlviewer-msvc16-x64" + + if (-not (Test-Path $buildDir)) { + mkdir -p $buildDir | Out-Null + } + + cd $buildDir + + # perform the configuration + cmake -G "Visual Studio 16 2019" -A x64 ` + -DMSVC_MULTIPLE_PROCESSES=ON ` + -DALLOW_DOWNLOADS=ON ` + -DSTATIC_BUILD=ON ` + -DOPENSSL_NO_CAPIENG=ON ` + ../orthanc-stone/Samples/Sdl/SimpleViewer + + $solutionPath = ls -filter *.sln + Write-Host "Solution file(s) available at: $solutionPath" +``` + +The initial configuration step will be quite lengthy, for CMake needs to +setup its internal cache based on your environment and build tools. + +Subsequent runs will be several orders of magnitude faster! + +One the solution (.sln) file is ready, you can open it using the Visual Studio +IDE and choose Build --> Build solution. + +An alternative is to execute `cmake --build .` in the build folder created by +the script. + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/BasicScene.cpp --- a/Samples/Sdl/BasicScene.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,418 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -// From Stone -#include "../../Framework/Viewport/SdlViewport.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/ZoomSceneTracker.h" -#include "../../Framework/Scene2DViewport/ViewportController.h" -#include "../../Framework/Scene2DViewport/UndoStack.h" - -#include "../../Framework/StoneInitialization.h" -#include "../../Framework/Messages/MessageBroker.h" - -// From Orthanc framework -#include -#include -#include -#include -#include - -#include - -#include -#include - -static const unsigned int FONT_SIZE = 32; -static const int LAYER_POSITION = 150; - -#define OPENGL_ENABLED 0 - -void PrepareScene(OrthancStone::Scene2D& scene) -{ - using namespace OrthancStone; - - // Texture of 2x2 size - { - Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); - - uint8_t *p = reinterpret_cast(i.GetRow(0)); - p[0] = 255; - p[1] = 0; - p[2] = 0; - - p[3] = 0; - p[4] = 255; - p[5] = 0; - - p = reinterpret_cast(i.GetRow(1)); - p[0] = 0; - p[1] = 0; - p[2] = 255; - - p[3] = 255; - p[4] = 0; - p[5] = 0; - - scene.SetLayer(12, new ColorTextureSceneLayer(i)); - - std::unique_ptr l(new ColorTextureSceneLayer(i)); - l->SetOrigin(-3, 2); - l->SetPixelSpacing(1.5, 1); - l->SetAngle(20.0 / 180.0 * M_PI); - scene.SetLayer(14, l.release()); - } - - // Texture of 1x1 size - { - Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); - - uint8_t *p = reinterpret_cast(i.GetRow(0)); - p[0] = 255; - p[1] = 0; - p[2] = 0; - - std::unique_ptr l(new ColorTextureSceneLayer(i)); - l->SetOrigin(-2, 1); - l->SetAngle(20.0 / 180.0 * M_PI); - scene.SetLayer(13, l.release()); - } - - // Some lines - { - std::unique_ptr layer(new PolylineSceneLayer); - - 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, 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, 0, 255, 0); - - double dy = 1.01; - chain.clear(); - chain.push_back(ScenePoint2D(-4, -4)); - 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, 0, 0, 255); - - scene.SetLayer(50, layer.release()); - } - - // Some text - { - std::unique_ptr layer(new TextSceneLayer); - layer->SetText("Hello"); - scene.SetLayer(100, layer.release()); - } -} - - -void TakeScreenshot(const std::string& target, - 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); - 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); -} - - -void HandleApplicationEvent(const SDL_Event& event, - boost::shared_ptr& controller, - boost::shared_ptr& activeTracker) -{ - using namespace OrthancStone; - - Scene2D& scene = controller->GetScene(); - IViewport& viewport = controller->GetViewport(); - - if (event.type == SDL_MOUSEMOTION) - { - int scancodeCount = 0; - const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); - - if (activeTracker.get() == NULL && - SDL_SCANCODE_LCTRL < scancodeCount && - keyboardState[SDL_SCANCODE_LCTRL]) - { - // The "left-ctrl" key is down, while no tracker is present - - PointerEvent e; - e.AddPosition(viewport.GetPixelCenterCoordinates(event.button.x, event.button.y)); - - ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); - - char buf[64]; - sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY()); - - if (scene.HasLayer(LAYER_POSITION)) - { - TextSceneLayer& layer = - dynamic_cast(scene.GetLayer(LAYER_POSITION)); - layer.SetText(buf); - layer.SetPosition(p.GetX(), p.GetY()); - } - else - { - std::unique_ptr - layer(new TextSceneLayer); - layer->SetColor(0, 255, 0); - layer->SetText(buf); - layer->SetBorder(20); - layer->SetAnchor(BitmapAnchor_BottomCenter); - layer->SetPosition(p.GetX(), p.GetY()); - scene.SetLayer(LAYER_POSITION, layer.release()); - } - } - else - { - scene.DeleteLayer(LAYER_POSITION); - } - } - else if (event.type == SDL_MOUSEBUTTONDOWN) - { - PointerEvent e; - e.AddPosition(viewport.GetPixelCenterCoordinates(event.button.x, event.button.y)); - - switch (event.button.button) - { - case SDL_BUTTON_MIDDLE: - activeTracker = boost::make_shared(controller, e); - break; - - case SDL_BUTTON_RIGHT: - activeTracker = boost::make_shared - (controller, e, viewport.GetCanvasHeight()); - break; - - case SDL_BUTTON_LEFT: - activeTracker = boost::make_shared(controller, e); - break; - - default: - break; - } - } - else if (event.type == SDL_KEYDOWN && - event.key.repeat == 0 /* Ignore key bounce */) - { - switch (event.key.keysym.sym) - { - case SDLK_s: - controller->FitContent(viewport.GetCanvasWidth(), - viewport.GetCanvasHeight()); - break; - - case SDLK_c: - TakeScreenshot("screenshot.png", scene, - viewport.GetCanvasWidth(), - viewport.GetCanvasHeight()); - break; - - default: - break; - } - } -} - -#if OPENGL_ENABLED==1 -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 - -void Run(OrthancStone::MessageBroker& broker, - OrthancStone::SdlViewport& viewport) -{ - using namespace OrthancStone; - - boost::shared_ptr controller( - new ViewportController(boost::make_shared(), broker, viewport)); - -#if OPENGL_ENABLED==1 - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(OpenGLMessageCallback, 0); -#endif - - boost::shared_ptr tracker; - - bool firstShown = true; - bool stop = false; - while (!stop) - { - viewport.Refresh(); - - SDL_Event event; - while (!stop && - SDL_PollEvent(&event)) - { - if (event.type == SDL_QUIT) - { - stop = true; - break; - } - else if (event.type == SDL_MOUSEMOTION) - { - if (tracker) - { - PointerEvent e; - e.AddPosition(viewport.GetPixelCenterCoordinates( - event.button.x, event.button.y)); - tracker->PointerMove(e); - } - } - else if (event.type == SDL_MOUSEBUTTONUP) - { - if (tracker) - { - PointerEvent e; - e.AddPosition(viewport.GetPixelCenterCoordinates( - event.button.x, event.button.y)); - tracker->PointerUp(e); - if(!tracker->IsAlive()) - tracker.reset(); - } - } - else if (event.type == SDL_WINDOWEVENT) - { - switch (event.window.event) - { - case SDL_WINDOWEVENT_SIZE_CHANGED: - tracker.reset(); - viewport.UpdateSize(event.window.data1, event.window.data2); - break; - - case SDL_WINDOWEVENT_SHOWN: - if (firstShown) - { - // Once the window is first shown, fit the content to its size - controller->FitContent(viewport.GetCanvasWidth(), viewport.GetCanvasHeight()); - firstShown = false; - } - - break; - - default: - break; - } - } - else if (event.type == SDL_KEYDOWN && - event.key.repeat == 0 /* Ignore key bounce */) - { - switch (event.key.keysym.sym) - { - case SDLK_f: - viewport.GetWindow().ToggleMaximize(); - break; - - case SDLK_q: - stop = true; - break; - - default: - break; - } - } - - HandleApplicationEvent(event, controller, tracker); - } - - SDL_Delay(1); - } -} - - - - -/** - * 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[]) -{ - OrthancStone::StoneInitialize(); - Orthanc::Logging::EnableInfoLevel(true); - - try - { -#if OPENGL_ENABLED==1 - OrthancStone::SdlOpenGLViewport viewport("Hello", 1024, 768); -#else - OrthancStone::SdlCairoViewport viewport("Hello", 1024, 768); -#endif - PrepareScene(viewport.GetScene()); - - viewport.GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, - FONT_SIZE, Orthanc::Encoding_Latin1); - - OrthancStone::MessageBroker broker; - Run(broker, viewport); - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "EXCEPTION: " << e.What(); - } - - OrthancStone::StoneFinalize(); - - return 0; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/CMakeLists.txt --- a/Samples/Sdl/CMakeLists.txt Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) - -##################################################################### -## Configuration of the Orthanc framework -##################################################################### - -# This CMake file defines the "ORTHANC_STONE_VERSION" macro, so it -# must be the first inclusion -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/Version.cmake) - -if (ORTHANC_STONE_VERSION STREQUAL "mainline") - set(ORTHANC_FRAMEWORK_VERSION "mainline") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") -else() - set(ORTHANC_FRAMEWORK_VERSION "1.5.7") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") -endif() - -set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") -set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") -set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") - - -##################################################################### -## Configuration of the Stone framework -##################################################################### - -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) - -DownloadPackage( - "a24b8136b8f3bb93f166baf97d9328de" - "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" - "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") - -set(ORTHANC_STONE_APPLICATION_RESOURCES - UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf - ) - -SET(ENABLE_SDL_CONSOLE OFF CACHE BOOL "Enable the use of the MIT-licensed SDL_Console") -SET(ENABLE_GOOGLE_TEST OFF) -SET(ENABLE_LOCALE ON) -SET(ENABLE_SDL ON) -SET(ENABLE_WEB_CLIENT ON) -SET(ORTHANC_SANDBOXED OFF) -LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) - -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) - -add_definitions( - -DORTHANC_ENABLE_LOGGING_PLUGIN=0 - ) - - -##################################################################### -## Build the samples -##################################################################### - -add_library(OrthancStone STATIC - ${ORTHANC_STONE_SOURCES} - ) - -# -# BasicScene -# - -add_executable(BasicScene - BasicScene.cpp - ) - -target_link_libraries(BasicScene OrthancStone) - -# -# TrackerSample -# - -LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp") -LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.cpp") -LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.h") - -if (MSVC AND MSVC_VERSION GREATER 1700) - LIST(APPEND TRACKERSAMPLE_SOURCE "cpp.hint") -endif() - -add_executable(TrackerSample - ${TRACKERSAMPLE_SOURCE} - ) - -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) - -# -# Multiplatform Basic Scene -# - -LIST(APPEND MP_BASIC_SCENE_SOURCE "../MultiPlatform/BasicScene/BasicScene.cpp") -LIST(APPEND MP_BASIC_SCENE_SOURCE "../MultiPlatform/BasicScene/BasicScene.h") -LIST(APPEND MP_BASIC_SCENE_SOURCE "../MultiPlatform/BasicScene/mainSdl.cpp") - -if (MSVC AND MSVC_VERSION GREATER 1700) - LIST(APPEND MP_BASIC_SCENE_SOURCE "cpp.hint") -endif() - -add_executable(MpBasicScene - ${MP_BASIC_SCENE_SOURCE} - ) - -target_include_directories(MpBasicScene PUBLIC ${ORTHANC_STONE_ROOT}) -target_link_libraries(MpBasicScene OrthancStone) diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/FusionMprSdl.cpp --- a/Samples/Sdl/FusionMprSdl.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,805 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "FusionMprSdl.h" - -#include "../../Framework/OpenGL/SdlOpenGLContext.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 -#include -#include -#include -#include - -#include -#include -#include - -#include -#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]; - } - - Scene2D& FusionMprSdlApp::GetScene() - { - return controller_->GetScene(); - } - - const Scene2D& FusionMprSdlApp::GetScene() const - { - return controller_->GetScene(); - } - - void FusionMprSdlApp::SelectNextTool() - { - currentTool_ = static_cast(currentTool_ + 1); - if (currentTool_ == FusionMprGuiTool_LAST) - currentTool_ = static_cast(0);; - printf("Current tool is now: %s\n", FusionMprMeasureToolToString(currentTool_)); - } - - void FusionMprSdlApp::DisplayInfoText() - { - // do not try to use stuff too early! - ICompositor* pCompositor = &(viewport_.GetCompositor()); - if (pCompositor == NULL) - return; - - std::stringstream msg; - - for (std::map::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( - GetScene().GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); - layerP = &layer; - } - else - { - std::unique_ptr 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 = viewport_.GetCompositor().GetCanvasWidth() * (-0.5); - double cY = viewport_.GetCompositor().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(GetScene().GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); - layer.SetText(buf); - layer.SetPosition(p.GetX(), p.GetY()); - } - else - { - std::unique_ptr 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(controller_->GetViewport().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(controller_->GetViewport().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(controller_->GetViewport().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(controller_->GetViewport().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(viewport_.GetCompositor().GetCanvasWidth(), - viewport_.GetCompositor().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", - viewport_.GetCompositor().GetCanvasWidth(), - viewport_.GetCompositor().GetCanvasHeight()); - break; - - default: - break; - } - } - } - - - void FusionMprSdlApp::OnSceneTransformChanged( - const ViewportController::SceneTransformChanged& message) - { - DisplayInfoText(); - } - - boost::shared_ptr FusionMprSdlApp::CreateSuitableTracker( - const SDL_Event & event, - const PointerEvent & e) - { - using namespace Orthanc; - - switch (event.button.button) - { - case SDL_BUTTON_MIDDLE: - return boost::shared_ptr(new PanSceneTracker - (controller_, e)); - - case SDL_BUTTON_RIGHT: - return boost::shared_ptr(new ZoomSceneTracker - (controller_, e, viewport_.GetCompositor().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 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(new RotateSceneTracker( - controller_, e)); - case FusionMprGuiTool_Pan: - return boost::shared_ptr(new PanSceneTracker( - controller_, e)); - case FusionMprGuiTool_Zoom: - return boost::shared_ptr(new ZoomSceneTracker( - controller_, e, viewport_.GetCompositor().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(new CreateLineMeasureTracker( - IObserver::GetBroker(), controller_, e)); - case FusionMprGuiTool_AngleMeasure: - return boost::shared_ptr(new CreateAngleMeasureTracker( - IObserver::GetBroker(), controller_, e)); - case FusionMprGuiTool_CircleMeasure: - LOG(ERROR) << "Not implemented yet!"; - return boost::shared_ptr(); - case FusionMprGuiTool_EllipseMeasure: - LOG(ERROR) << "Not implemented yet!"; - return boost::shared_ptr(); - default: - throw OrthancException(ErrorCode_InternalError, "Wrong tool!"); - } - } - } - default: - return boost::shared_ptr(); - } - } - - - FusionMprSdlApp::FusionMprSdlApp(MessageBroker& broker) - : IObserver(broker) - , broker_(broker) - , oracleObservable_(broker) - , oracle_(*this) - , currentTool_(FusionMprGuiTool_Rotate) - , undoStack_(new UndoStack) - , viewport_("Hello", 1024, 1024, false) // False means we do NOT let Windows treat this as a legacy application that needs to be scaled - { - //oracleObservable.RegisterObserverCallback - //(new Callable - // (*this, &FusionMprSdlApp::Handle)); - - //oracleObservable.RegisterObserverCallback - //(new Callable - // (*this, &FusionMprSdlApp::Handle)); - - //oracleObservable.RegisterObserverCallback - //(new Callable - // (*this, &ToFusionMprSdlAppto::Handle)); - - oracleObservable_.RegisterObserverCallback - (new Callable - (*this, &FusionMprSdlApp::Handle)); - - controller_ = boost::shared_ptr( - new ViewportController(undoStack_, broker_, viewport_)); - - controller_->RegisterObserverCallback( - new Callable - (*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(i.GetRow(0)); - p[0] = 255; - p[1] = 0; - p[2] = 0; - - p[3] = 0; - p[4] = 255; - p[5] = 0; - - p = reinterpret_cast(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 FusionMprSdlApp::TrackerHitTest(const PointerEvent & e) - { - // std::vector> measureTools_; - return boost::shared_ptr(); - } - - 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 - (message.GetCommand()).GetUri().c_str()); - break; - - default: - break; - } - } - - void FusionMprSdlApp::SetVolume1(int depth, - const boost::shared_ptr& 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& 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& 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 - controller_->FitContent(viewport_.GetCanvasWidth(), viewport_.GetCanvasHeight()); - - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(OpenGLMessageCallback, 0); - - viewport_.GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, - FONT_SIZE_0, Orthanc::Encoding_Latin1); - viewport_.GetCompositor().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 ct(new DicomVolumeImage); - boost::shared_ptr dose(new DicomVolumeImage); - - - boost::shared_ptr ctLoader; - boost::shared_ptr doseLoader; - boost::shared_ptr 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 - // (*this, &FusionMprSdlApp::Handle)); - ctLoader->RegisterObserverCallback - (new Callable - (*this, &FusionMprSdlApp::Handle)); - - this->SetVolume1(0, ctLoader, new GrayscaleStyleConfigurator); - - { - std::unique_ptr config(new LookupTableStyleConfigurator); - config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); - - boost::shared_ptr 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) - { - viewport_.GetCompositor().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); - } - else if (event.type == SDL_KEYDOWN && - event.key.repeat == 0 /* Ignore key bounce */) - { - switch (event.key.keysym.sym) - { - case SDLK_f: - viewport_.GetWindow().ToggleMaximize(); - break; - - case SDLK_s: - controller_->FitContent(viewport_.GetCanvasWidth(), viewport_.GetCanvasHeight()); - break; - - case SDLK_q: - g_stopApplication = true; - break; - default: - break; - } - } - HandleApplicationEvent(event); - } - SDL_Delay(1); - } - - //// 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 g_app; - -void FusionMprSdl_SetInfoDisplayMessage(std::string key, std::string value) -{ - boost::shared_ptr 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 app(new FusionMprSdlApp(broker)); - g_app = app; - app->PrepareScene(); - app->Run(); - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "EXCEPTION: " << e.What(); - } - - StoneFinalize(); - - return 0; -} - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/FusionMprSdl.h --- a/Samples/Sdl/FusionMprSdl.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,206 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "../../Framework/Viewport/SdlViewport.h" - -#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 -#include -#include - -#include - -namespace OrthancStone -{ - class OpenGLCompositor; - class IVolumeSlicer; - class ILayerStyleConfigurator; - class DicomStructureSetLoader; - class IOracle; - class ThreadedOracle; - class VolumeSceneLayerSource; - class NativeFusionMprApplicationContext; - class SdlOpenGLViewport; - - 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 - , public IMessageEmitter - { - public: - // 12 because. - FusionMprSdlApp(MessageBroker& broker); - - void PrepareScene(); - void Run(); - void SetInfoDisplayMessage(std::string key, std::string value); - void DisableTracker(); - - Scene2D& GetScene(); - 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 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 TrackerHitTest(const PointerEvent& e); - - boost::shared_ptr 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 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& volume, - ILayerStyleConfigurator* style); - - void SetVolume2( - int depth, - const boost::shared_ptr& volume, - ILayerStyleConfigurator* style); - - void SetStructureSet( - int depth, - const boost::shared_ptr& volume); - - - - private: - void DisplayFloatingCtrlInfoText(const PointerEvent& e); - void DisplayInfoText(); - void HideInfoText(); - - private: - CoordinateSystem3D plane_; - - boost::shared_ptr source1_, source2_, source3_; - - /** - 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 controller_; - - std::map infoTextMap_; - boost::shared_ptr 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_; - SdlOpenGLViewport viewport_; - }; - -} - - - \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/Loader.cpp --- a/Samples/Sdl/Loader.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,518 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#include "../../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/Volumes/VolumeSceneLayerSource.h" -#include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h" -#include "../../Framework/Volumes/DicomVolumeImageReslicer.h" - -// From Orthanc framework -#include -#include -#include -#include -#include - - -namespace OrthancStone -{ - class NativeApplicationContext : public IMessageEmitter - { - private: - boost::shared_mutex mutex_; - MessageBroker broker_; - IObservable oracleObservable_; - - public: - NativeApplicationContext() : - oracleObservable_(broker_) - { - } - - - virtual void EmitMessage(const IObserver& observer, - const IMessage& message) ORTHANC_OVERRIDE - { - try - { - boost::unique_lock 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: - NativeApplicationContext& that_; - boost::shared_lock lock_; - - public: - ReaderLock(NativeApplicationContext& that) : - that_(that), - lock_(that.mutex_) - { - } - }; - - - class WriterLock : public boost::noncopyable - { - private: - NativeApplicationContext& that_; - boost::unique_lock lock_; - - public: - WriterLock(NativeApplicationContext& that) : - that_(that), - lock_(that.mutex_) - { - } - - MessageBroker& GetBroker() - { - return that_.broker_; - } - - IObservable& GetOracleObservable() - { - return that_.oracleObservable_; - } - }; - }; -} - - - -class Toto : public OrthancStone::IObserver -{ -private: - OrthancStone::CoordinateSystem3D plane_; - OrthancStone::IOracle& oracle_; - OrthancStone::Scene2D scene_; - std::unique_ptr 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& >(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); - - printf("ICI [%s]\n", v.toStyledString().c_str()); - } - - void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message) - { - printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); - } - - void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message) - { - printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); - } - - 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 OrthancStone::IOracleCommand::Type_GetOrthancWebViewerJpeg: - printf("URI: [%s]\n", dynamic_cast - (message.GetCommand()).GetUri().c_str()); - break; - - default: - break; - } - } - -public: - Toto(OrthancStone::IOracle& oracle, - OrthancStone::IObservable& oracleObservable) : - IObserver(oracleObservable.GetBroker()), - oracle_(oracle) - { - oracleObservable.RegisterObserverCallback - (new OrthancStone::Callable - (*this, &Toto::Handle)); - - oracleObservable.RegisterObserverCallback - (new OrthancStone::Callable - (*this, &Toto::Handle)); - - oracleObservable.RegisterObserverCallback - (new OrthancStone::Callable - (*this, &Toto::Handle)); - - oracleObservable.RegisterObserverCallback - (new OrthancStone::Callable - (*this, &Toto::Handle)); - - oracleObservable.RegisterObserverCallback - (new OrthancStone::Callable - (*this, &Toto::Handle)); - } - - void SetReferenceLoader(OrthancStone::IObservable& loader) - { - loader.RegisterObserverCallback - (new OrthancStone::Callable - (*this, &Toto::Handle)); - } - - void SetVolume1(int depth, - const boost::shared_ptr& volume, - OrthancStone::ILayerStyleConfigurator* style) - { - source1_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); - - if (style != NULL) - { - source1_->SetConfigurator(style); - } - } - - void SetVolume2(int depth, - const boost::shared_ptr& volume, - OrthancStone::ILayerStyleConfigurator* style) - { - source2_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); - - if (style != NULL) - { - source2_->SetConfigurator(style); - } - } - - void SetStructureSet(int depth, - const boost::shared_ptr& volume) - { - source3_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); - } - -}; - - -void Run(OrthancStone::NativeApplicationContext& context, - OrthancStone::ThreadedOracle& oracle) -{ - // the oracle has been supplied with the context (as an IEmitter) upon - // creation - boost::shared_ptr ct(new OrthancStone::DicomVolumeImage); - boost::shared_ptr dose(new OrthancStone::DicomVolumeImage); - - - boost::shared_ptr toto; - boost::shared_ptr ctLoader; - boost::shared_ptr doseLoader; - boost::shared_ptr rtstructLoader; - - { - 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())); - } - - - //toto->SetReferenceLoader(*ctLoader); - toto->SetReferenceLoader(*doseLoader); - - -#if 1 - toto->SetVolume1(0, ctLoader, new OrthancStone::GrayscaleStyleConfigurator); -#else - { - boost::shared_ptr reslicer(new OrthancStone::DicomVolumeImageReslicer(ct)); - toto->SetVolume1(0, reslicer, new OrthancStone::GrayscaleStyleConfigurator); - } -#endif - - - { - std::unique_ptr config(new OrthancStone::LookupTableStyleConfigurator); - config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); - - boost::shared_ptr 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::unique_ptr command(new OrthancStone::OrthancRestApiCommand); - command->SetMethod(Orthanc::HttpMethod_Post); - command->SetUri("/tools/find"); - command->SetBody(v); - - oracle.Schedule(*toto, command.release()); - } - - if(0) - { - if (0) - { - std::unique_ptr 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::unique_ptr 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::unique_ptr 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::unique_ptr 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::unique_ptr 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::unique_ptr 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::unique_ptr command(new OrthancStone::SleepOracleCommand(i * 1000)); - command->SetPayload(new Orthanc::SingleValueObject(42 * i)); - 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 - - // Delphine - //ctLoader->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT - //ctLoader->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); // Lung 1/10mm - - - { - LOG(WARNING) << "...Waiting for Ctrl-C..."; - - oracle.Start(); - - 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(); - } -} - - - -/** - * 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[]) -{ - OrthancStone::StoneInitialize(); - //Orthanc::Logging::EnableInfoLevel(true); - - try - { - OrthancStone::NativeApplicationContext context; - - OrthancStone::ThreadedOracle oracle(context); - //oracle.SetThreadsCount(1); - - { - Orthanc::WebServiceParameters p; - //p.SetUrl("http://localhost:8043/"); - p.SetCredentials("orthanc", "orthanc"); - oracle.SetOrthancParameters(p); - } - - //oracle.Start(); - - Run(context, oracle); - - //oracle.Stop(); - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "EXCEPTION: " << e.What(); - } - - OrthancStone::StoneFinalize(); - - return 0; -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/RadiographyEditor.cpp --- a/Samples/Sdl/RadiographyEditor.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,267 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "../Shared/RadiographyEditorApp.h" - -// From Stone -#include "../../Framework/Oracle/SleepOracleCommand.h" -#include "../../Framework/Oracle/ThreadedOracle.h" -#include "../../Applications/Sdl/SdlOpenGLWindow.h" -#include "../../Framework/Scene2D/OpenGLCompositor.h" -#include "../../Framework/Scene2D/CairoCompositor.h" -#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" -#include "../../Framework/Scene2D/OpenGLCompositor.h" -#include "../../Framework/StoneInitialization.h" - -#include -#include - - -#include -#include - -#include -#include - -using namespace OrthancStone; - -namespace OrthancStone -{ - class NativeApplicationContext : public IMessageEmitter - { - private: - boost::shared_mutex mutex_; - MessageBroker broker_; - IObservable oracleObservable_; - - public: - NativeApplicationContext() : - oracleObservable_(broker_) - { - } - - - virtual void EmitMessage(const IObserver& observer, - const IMessage& message) ORTHANC_OVERRIDE - { - try - { - boost::unique_lock 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: - NativeApplicationContext& that_; - boost::shared_lock lock_; - - public: - ReaderLock(NativeApplicationContext& that) : - that_(that), - lock_(that.mutex_) - { - } - }; - - - class WriterLock : public boost::noncopyable - { - private: - NativeApplicationContext& that_; - boost::unique_lock lock_; - - public: - WriterLock(NativeApplicationContext& that) : - that_(that), - lock_(that.mutex_) - { - } - - MessageBroker& GetBroker() - { - return that_.broker_; - } - - IObservable& GetOracleObservable() - { - return that_.oracleObservable_; - } - }; - }; -} - -class OpenGlSdlCompositorFactory : public ICompositorFactory -{ - OpenGL::IOpenGLContext& openGlContext_; - -public: - OpenGlSdlCompositorFactory(OpenGL::IOpenGLContext& openGlContext) : - openGlContext_(openGlContext) - {} - - ICompositor* GetCompositor(const Scene2D& scene) - { - - OpenGLCompositor* compositor = new OpenGLCompositor(openGlContext_, scene); - 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); - return compositor; - } -}; - -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); - } -} - - -/** - * 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::NativeApplicationContext context; - OrthancStone::NativeApplicationContext::WriterLock lock(context); - OrthancStone::ThreadedOracle oracle(context); - - // False means we do NOT let Windows treat this as a legacy application - // that needs to be scaled - SdlOpenGLWindow window("Hello", 1024, 1024, false); - - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(OpenGLMessageCallback, 0); - - std::unique_ptr compositorFactory(new OpenGlSdlCompositorFactory(window)); - boost::shared_ptr app(new RadiographyEditorApp(oracle, lock.GetOracleObservable(), compositorFactory.release())); - app->PrepareScene(); - app->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); - - bool stopApplication = false; - - while (!stopApplication) - { - app->Refresh(); - - SDL_Event event; - while (!stopApplication && SDL_PollEvent(&event)) - { - OrthancStone::KeyboardModifiers modifiers = OrthancStone::KeyboardModifiers_None; - if (event.key.keysym.mod & KMOD_CTRL) - modifiers = static_cast(static_cast(modifiers) | static_cast(OrthancStone::KeyboardModifiers_Control)); - if (event.key.keysym.mod & KMOD_ALT) - modifiers = static_cast(static_cast(modifiers) | static_cast(OrthancStone::KeyboardModifiers_Alt)); - if (event.key.keysym.mod & KMOD_SHIFT) - modifiers = static_cast(static_cast(modifiers) | static_cast(OrthancStone::KeyboardModifiers_Shift)); - - OrthancStone::MouseButton button; - if (event.button.button == SDL_BUTTON_LEFT) - button = OrthancStone::MouseButton_Left; - else if (event.button.button == SDL_BUTTON_MIDDLE) - button = OrthancStone::MouseButton_Middle; - else if (event.button.button == SDL_BUTTON_RIGHT) - button = OrthancStone::MouseButton_Right; - - if (event.type == SDL_QUIT) - { - stopApplication = true; - break; - } - else if (event.type == SDL_WINDOWEVENT && - event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) - { - app->DisableTracker(); // was: tracker.reset(NULL); - app->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_q: - stopApplication = true; - break; - default: - { - app->OnKeyPressed(event.key.keysym.sym, modifiers); - } - } - } - else if (event.type == SDL_MOUSEBUTTONDOWN) - { - app->OnMouseDown(event.button.x, event.button.y, modifiers, button); - } - else if (event.type == SDL_MOUSEMOTION) - { - app->OnMouseMove(event.button.x, event.button.y, modifiers); - } - else if (event.type == SDL_MOUSEBUTTONUP) - { - app->OnMouseUp(event.button.x, event.button.y, modifiers, button); - } - } - SDL_Delay(1); - } - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "EXCEPTION: " << e.What(); - } - - StoneFinalize(); - - return 0; -} - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/SimpleViewer/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/SimpleViewer/CMakeLists.txt Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 2.8.10) + +project(SdlSimpleViewer) + +set(ORTHANC_FRAMEWORK_SOURCE "path") +set(ORTHANC_FRAMEWORK_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../orthanc) +set(STONE_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../) + +include(${STONE_ROOT}/Resources/CMake/OrthancStoneParameters.cmake) + +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ENABLE_QT OFF) +SET(ENABLE_SDL ON) +SET(ENABLE_DCMTK ON) # <== +SET(ENABLE_OPENGL ON) # <== +SET(ENABLE_WEB_CLIENT ON) +SET(ORTHANC_SANDBOXED OFF) + +include(${STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) +include(${STONE_ROOT}/Resources/CMake/Utilities.cmake) + +include_directories(${STONE_ROOT}) + +add_definitions( + -DORTHANC_ENABLE_LOGGING=1 + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + -DORTHANC_ENABLE_PUGIXML=0 + -DORTHANC_DEFAULT_DICOM_ENCODING=Encoding_Latin1 + ) + +SortFilesInSourceGroups() + +add_executable(SdlSimpleViewer + SdlSimpleViewerApplication.h + SdlSimpleViewer.cpp + ${ORTHANC_STONE_SOURCES} + ) + + +target_link_libraries(SdlSimpleViewer ${DCMTK_LIBRARIES}) + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/SimpleViewer/CMakeSettings.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/SimpleViewer/CMakeSettings.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,37 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [ + { + "name": "MSVC_MULTIPLE_PROCESSES", + "value": "True", + "type": "BOOL" + }, + { + "name": "ALLOW_DOWNLOADS", + "value": "True", + "type": "BOOL" + }, + { + "name": "STATIC_BUILD", + "value": "True", + "type": "BOOL" + }, + { + "name": "OPENSSL_NO_CAPIENG", + "value": "True", + "type": "BOOL" + }, + ] + } + ] +} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/SimpleViewer/SdlSimpleViewer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/SimpleViewer/SdlSimpleViewer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,292 @@ + +#include "SdlSimpleViewerApplication.h" + +#include + +#include +#include +#include +#include +#include + +#include + +namespace OrthancStone +{ + static KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState, + const int scancodeCount) + { + int result = KeyboardModifiers_None; + + if (keyboardState != NULL) + { + if (SDL_SCANCODE_LSHIFT < scancodeCount && + keyboardState[SDL_SCANCODE_LSHIFT]) + { + result |= KeyboardModifiers_Shift; + } + + if (SDL_SCANCODE_RSHIFT < scancodeCount && + keyboardState[SDL_SCANCODE_RSHIFT]) + { + result |= KeyboardModifiers_Shift; + } + + if (SDL_SCANCODE_LCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_LCTRL]) + { + result |= KeyboardModifiers_Control; + } + + if (SDL_SCANCODE_RCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_RCTRL]) + { + result |= KeyboardModifiers_Control; + } + + if (SDL_SCANCODE_LALT < scancodeCount && + keyboardState[SDL_SCANCODE_LALT]) + { + result |= KeyboardModifiers_Alt; + } + + if (SDL_SCANCODE_RALT < scancodeCount && + keyboardState[SDL_SCANCODE_RALT]) + { + result |= KeyboardModifiers_Alt; + } + } + + return static_cast(result); + } + + + static void GetPointerEvent(PointerEvent& p, + const ICompositor& compositor, + SDL_Event event, + const uint8_t* keyboardState, + const int scancodeCount) + { + KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); + + switch (event.button.button) + { + case SDL_BUTTON_LEFT: + p.SetMouseButton(OrthancStone::MouseButton_Left); + break; + + case SDL_BUTTON_RIGHT: + p.SetMouseButton(OrthancStone::MouseButton_Right); + break; + + case SDL_BUTTON_MIDDLE: + p.SetMouseButton(OrthancStone::MouseButton_Middle); + break; + + default: + p.SetMouseButton(OrthancStone::MouseButton_None); + break; + } + + p.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + p.SetAltModifier(modifiers & KeyboardModifiers_Alt); + p.SetControlModifier(modifiers & KeyboardModifiers_Control); + p.SetShiftModifier(modifiers & KeyboardModifiers_Shift); + } + +} + +/** + * 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[]) +{ + try + { + OrthancStone::StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + //Orthanc::Logging::EnableTraceLevel(true); + + { + +#if 1 + boost::shared_ptr viewport = + OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600); +#else + boost::shared_ptr viewport = + OrthancStone::SdlCairoViewport::Create("Stone of Orthanc", 800, 600); +#endif + + OrthancStone::GenericLoadersContext context(1, 4, 1); + + context.StartOracle(); + + { + + boost::shared_ptr application( + SdlSimpleViewerApplication::Create(context, viewport)); + + OrthancStone::DicomSource source; + + // Default and command-line parameters + const char* instanceId = "285dece8-e1956b38-cdc7d084-6ce3371e-536a9ffc"; + unsigned int frameIndex = 0; + + if (argc == 1) + { + LOG(ERROR) << "No instanceId supplied. The default of " << instanceId << " will be used. " + << "Please supply the Orthanc instance ID of the frame you wish to display then, optionally, " + << "the zero-based index of the frame (for multi-frame instances)"; + // TODO: frame number as second argument... + } + + if (argc >= 2) + instanceId = argv[1]; + + if (argc >= 3) + frameIndex = atoi(argv[1]); + + if (argc > 3) + { + LOG(ERROR) << "Extra arguments ignored!"; + } + + + application->LoadOrthancFrame(source, instanceId, frameIndex); + + OrthancStone::DefaultViewportInteractor interactor; + + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + bool stop = false; + while (!stop) + { + bool paint = false; + SDL_Event event; + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + stop = true; + break; + } + else if (viewport->IsRefreshEvent(event)) + { + paint = true; + } + else if (event.type == SDL_WINDOWEVENT && + (event.window.event == SDL_WINDOWEVENT_RESIZED || + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) + { + viewport->UpdateSize(event.window.data1, event.window.data2); + } + else if (event.type == SDL_WINDOWEVENT && + (event.window.event == SDL_WINDOWEVENT_SHOWN || + event.window.event == SDL_WINDOWEVENT_EXPOSED)) + { + paint = true; + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + viewport->ToggleMaximize(); + break; + + case SDLK_s: + application->FitContent(); + break; + + case SDLK_q: + stop = true; + break; + + default: + break; + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN || + event.type == SDL_MOUSEMOTION || + event.type == SDL_MOUSEBUTTONUP) + { + std::auto_ptr lock(viewport->Lock()); + if (lock->HasCompositor()) + { + OrthancStone::PointerEvent p; + OrthancStone::GetPointerEvent(p, lock->GetCompositor(), + event, keyboardState, scancodeCount); + + switch (event.type) + { + case SDL_MOUSEBUTTONDOWN: + lock->GetController().HandleMousePress(interactor, p, + lock->GetCompositor().GetCanvasWidth(), + lock->GetCompositor().GetCanvasHeight()); + lock->Invalidate(); + break; + + case SDL_MOUSEMOTION: + if (lock->GetController().HandleMouseMove(p)) + { + lock->Invalidate(); + } + break; + + case SDL_MOUSEBUTTONUP: + lock->GetController().HandleMouseRelease(p); + lock->Invalidate(); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + } + + if (paint) + { + viewport->Paint(); + } + + // Small delay to avoid using 100% of CPU + SDL_Delay(1); + } + } + + context.StopOracle(); + } + } + + OrthancStone::StoneFinalize(); + return 0; + } + catch (Orthanc::OrthancException & e) + { + auto test = e.What(); + fprintf(stdout, test); + LOG(ERROR) << "OrthancException: " << e.What(); + return -1; + } + catch (OrthancStone::StoneException & e) + { + LOG(ERROR) << "StoneException: " << e.What(); + return -1; + } + catch (std::runtime_error & e) + { + LOG(ERROR) << "Runtime error: " << e.what(); + return -1; + } + catch (...) + { + LOG(ERROR) << "Native exception"; + return -1; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/SimpleViewer/SdlSimpleViewerApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/SimpleViewer/SdlSimpleViewerApplication.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,146 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + + +using OrthancStone::ILoadersContext; +using OrthancStone::ObserverBase; +using OrthancStone::IViewport; +using OrthancStone::DicomResourcesLoader; +using OrthancStone::SeriesFramesLoader; +using OrthancStone::TextureBaseSceneLayer; +using OrthancStone::DicomSource; +using OrthancStone::SeriesThumbnailsLoader; +using OrthancStone::LoadedDicomResources; +using OrthancStone::SeriesThumbnailType; +using OrthancStone::OracleScheduler; +using OrthancStone::OrthancRestApiCommand; +using OrthancStone::OracleScheduler; +using OrthancStone::OracleScheduler; +using OrthancStone::OracleScheduler; + + +class SdlSimpleViewerApplication : public ObserverBase +{ + +public: + static boost::shared_ptr Create(ILoadersContext& context, boost::shared_ptr viewport) + { + boost::shared_ptr application(new SdlSimpleViewerApplication(context, viewport)); + + { + std::auto_ptr lock(context.Lock()); + DicomResourcesLoader::Factory f; + application->dicomLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + } + + application->Register(*application->dicomLoader_, &SdlSimpleViewerApplication::Handle); + + return application; + } + + void LoadOrthancFrame(const DicomSource& source, const std::string& instanceId, unsigned int frame) + { + std::auto_ptr lock(context_.Lock()); + + dicomLoader_->ScheduleLoadOrthancResource(boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), + 0, source, Orthanc::ResourceType_Instance, instanceId, + new Orthanc::SingleValueObject(frame)); + } + +#if 0 + void LoadDicomWebFrame(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + unsigned int frame) + { + std::auto_ptr lock(context_.Lock()); + + // We first must load the "/metadata" to know the number of frames + dicomLoader_->ScheduleGetDicomWeb( + boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, + "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", + new Orthanc::SingleValueObject(frame)); + } +#endif + + void FitContent() + { + std::auto_ptr lock(viewport_->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + +private: + ILoadersContext& context_; + boost::shared_ptr viewport_; + boost::shared_ptr dicomLoader_; + boost::shared_ptr framesLoader_; + + SdlSimpleViewerApplication(ILoadersContext& context, + boost::shared_ptr viewport) : + context_(context), + viewport_(viewport) + { + } + + void Handle(const SeriesFramesLoader::FrameLoadedMessage& message) + { + LOG(INFO) << "Frame decoded! " + << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight() + << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat()); + + std::auto_ptr layer( + message.GetInstanceParameters().CreateTexture(message.GetImage())); + layer->SetLinearInterpolation(true); + + { + std::auto_ptr lock(viewport_->Lock()); + lock->GetController().GetScene().SetLayer(0, layer.release()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + } + + void Handle(const DicomResourcesLoader::SuccessMessage& message) + { + if (message.GetResources()->GetSize() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + //message.GetResources()->GetResource(0).Print(stdout); + + { + std::auto_ptr lock(context_.Lock()); + SeriesFramesLoader::Factory f(*message.GetResources()); + + framesLoader_ = boost::dynamic_pointer_cast( + f.Create(*lock)); + + Register( + *framesLoader_, &SdlSimpleViewerApplication::Handle); + + assert(message.HasUserPayload()); + + const Orthanc::SingleValueObject& payload = + dynamic_cast&>( + message.GetUserPayload()); + + LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue(); + framesLoader_->ScheduleLoadFrame( + 0, message.GetDicomSource(), payload.GetValue(), + message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */, + NULL); + } + } + +}; + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/SimpleViewer/SimpleViewer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/SimpleViewer/SimpleViewer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,292 @@ + +#include "SdlSimpleViewerApplication.h" + +#include + +#include +#include +#include +#include +#include + +#include + +namespace OrthancStone +{ + static KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState, + const int scancodeCount) + { + int result = KeyboardModifiers_None; + + if (keyboardState != NULL) + { + if (SDL_SCANCODE_LSHIFT < scancodeCount && + keyboardState[SDL_SCANCODE_LSHIFT]) + { + result |= KeyboardModifiers_Shift; + } + + if (SDL_SCANCODE_RSHIFT < scancodeCount && + keyboardState[SDL_SCANCODE_RSHIFT]) + { + result |= KeyboardModifiers_Shift; + } + + if (SDL_SCANCODE_LCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_LCTRL]) + { + result |= KeyboardModifiers_Control; + } + + if (SDL_SCANCODE_RCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_RCTRL]) + { + result |= KeyboardModifiers_Control; + } + + if (SDL_SCANCODE_LALT < scancodeCount && + keyboardState[SDL_SCANCODE_LALT]) + { + result |= KeyboardModifiers_Alt; + } + + if (SDL_SCANCODE_RALT < scancodeCount && + keyboardState[SDL_SCANCODE_RALT]) + { + result |= KeyboardModifiers_Alt; + } + } + + return static_cast(result); + } + + + static void GetPointerEvent(PointerEvent& p, + const ICompositor& compositor, + SDL_Event event, + const uint8_t* keyboardState, + const int scancodeCount) + { + KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); + + switch (event.button.button) + { + case SDL_BUTTON_LEFT: + p.SetMouseButton(OrthancStone::MouseButton_Left); + break; + + case SDL_BUTTON_RIGHT: + p.SetMouseButton(OrthancStone::MouseButton_Right); + break; + + case SDL_BUTTON_MIDDLE: + p.SetMouseButton(OrthancStone::MouseButton_Middle); + break; + + default: + p.SetMouseButton(OrthancStone::MouseButton_None); + break; + } + + p.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + p.SetAltModifier(modifiers & KeyboardModifiers_Alt); + p.SetControlModifier(modifiers & KeyboardModifiers_Control); + p.SetShiftModifier(modifiers & KeyboardModifiers_Shift); + } + +} + +/** + * 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[]) +{ + try + { + OrthancStone::StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + //Orthanc::Logging::EnableTraceLevel(true); + + { + +#if 1 + boost::shared_ptr viewport = + OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600); +#else + boost::shared_ptr viewport = + OrthancStone::SdlCairoViewport::Create("Stone of Orthanc", 800, 600); +#endif + + OrthancStone::GenericLoadersContext context(1, 4, 1); + + context.StartOracle(); + + { + + boost::shared_ptr application( + SdlSimpleViewerApplication::Create(context, viewport)); + + OrthancStone::DicomSource source; + + // Default and command-line parameters + const char* instanceId = "285dece8-e1956b38-cdc7d084-6ce3371e-536a9ffc"; + unsigned int frameIndex = 0; + + if (argc == 1) + { + LOG(ERROR) << "No instanceId supplied. The default of " << instanceId << " will be used. " + << "Please supply the Orthanc instance ID of the frame you wish to display then, optionally, " + << "the zero-based index of the frame (for multi-frame instances)"; + // TODO: frame number as second argument... + } + + if (argc >= 2) + instanceId = argv[1]; + + if (argc >= 3) + frameIndex = atoi(argv[1]); + + if (argc > 3) + { + LOG(ERROR) << "Extra arguments ignored!"; + } + + + application->LoadOrthancFrame(source, instanceId, frameIndex); + + OrthancStone::DefaultViewportInteractor interactor; + + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + bool stop = false; + while (!stop) + { + bool paint = false; + SDL_Event event; + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + stop = true; + break; + } + else if (viewport->IsRefreshEvent(event)) + { + paint = true; + } + else if (event.type == SDL_WINDOWEVENT && + (event.window.event == SDL_WINDOWEVENT_RESIZED || + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) + { + viewport->UpdateSize(event.window.data1, event.window.data2); + } + else if (event.type == SDL_WINDOWEVENT && + (event.window.event == SDL_WINDOWEVENT_SHOWN || + event.window.event == SDL_WINDOWEVENT_EXPOSED)) + { + paint = true; + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + viewport->ToggleMaximize(); + break; + + case SDLK_s: + application->FitContent(); + break; + + case SDLK_q: + stop = true; + break; + + default: + break; + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN || + event.type == SDL_MOUSEMOTION || + event.type == SDL_MOUSEBUTTONUP) + { + std::auto_ptr lock(viewport->Lock()); + if (lock->HasCompositor()) + { + OrthancStone::PointerEvent p; + OrthancStone::GetPointerEvent(p, lock->GetCompositor(), + event, keyboardState, scancodeCount); + + switch (event.type) + { + case SDL_MOUSEBUTTONDOWN: + lock->GetController().HandleMousePress(interactor, p, + lock->GetCompositor().GetCanvasWidth(), + lock->GetCompositor().GetCanvasHeight()); + lock->Invalidate(); + break; + + case SDL_MOUSEMOTION: + if (lock->GetController().HandleMouseMove(p)) + { + lock->Invalidate(); + } + break; + + case SDL_MOUSEBUTTONUP: + lock->GetController().HandleMouseRelease(p); + lock->Invalidate(); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + } + + if (paint) + { + viewport->Paint(); + } + + // Small delay to avoid using 100% of CPU + SDL_Delay(1); + } + } + + context.StopOracle(); + } + } + + OrthancStone::StoneFinalize(); + return 0; + } + catch (Orthanc::OrthancException & e) + { + auto test = e.What(); + fprintf(stdout, test); + LOG(ERROR) << "OrthancException: " << e.What(); + return -1; + } + catch (OrthancStone::StoneException & e) + { + LOG(ERROR) << "StoneException: " << e.What(); + return -1; + } + catch (std::runtime_error & e) + { + LOG(ERROR) << "Runtime error: " << e.what(); + return -1; + } + catch (...) + { + LOG(ERROR) << "Native exception"; + return -1; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/TrackerSample.cpp --- a/Samples/Sdl/TrackerSample.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "TrackerSampleApp.h" - - // From Stone -#include "../../Framework/OpenGL/SdlOpenGLContext.h" -#include "../../Framework/Scene2D/CairoCompositor.h" -#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" -#include "../../Framework/Scene2D/OpenGLCompositor.h" -#include "../../Framework/StoneInitialization.h" - -#include -#include - - -#include -#include - -#include -#include - -/* -TODO: - -- to decouple the trackers from the sample, we need to supply them with - the scene rather than the app - -- in order to do that, we need a GetNextFreeZIndex function (or something - along those lines) in the scene object - -*/ - -boost::weak_ptr g_app; - -void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value) -{ - boost::shared_ptr 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 - { - MessageBroker broker; - boost::shared_ptr app(new TrackerSampleApp(broker)); - g_app = app; - app->PrepareScene(); - app->Run(); - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "EXCEPTION: " << e.What(); - } - - StoneFinalize(); - - return 0; -} - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/TrackerSampleApp.cpp --- a/Samples/Sdl/TrackerSampleApp.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,733 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - -#include "TrackerSampleApp.h" - -#include "../../Framework/OpenGL/SdlOpenGLContext.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 -#include -#include -#include -#include - -#include -#include -#include - -#include - -namespace OrthancStone -{ - const char* MeasureToolToString(size_t i) - { - static const char* descs[] = { - "GuiTool_Rotate", - "GuiTool_Pan", - "GuiTool_Zoom", - "GuiTool_LineMeasure", - "GuiTool_CircleMeasure", - "GuiTool_AngleMeasure", - "GuiTool_EllipseMeasure", - "GuiTool_LAST" - }; - if (i >= GuiTool_LAST) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong tool index"); - } - return descs[i]; - } - - void TrackerSampleApp::SelectNextTool() - { - currentTool_ = static_cast(currentTool_ + 1); - if (currentTool_ == GuiTool_LAST) - currentTool_ = static_cast(0);; - printf("Current tool is now: %s\n", MeasureToolToString(currentTool_)); - } - - void TrackerSampleApp::DisplayInfoText() - { - // do not try to use stuff too early! - std::stringstream msg; - - for (std::map::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 (controller_->GetScene().HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX)) - { - TextSceneLayer& layer = dynamic_cast( - controller_->GetScene().GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); - layerP = &layer; - } - else - { - std::unique_ptr 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); - controller_->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 = GetCompositor().GetCanvasWidth() * (-0.5); - double cY = GetCompositor().GetCanvasHeight() * (-0.5); - controller_->GetScene().GetCanvasToSceneTransform().Apply(cX,cY); - layerP->SetPosition(cX, cY); - } - - void TrackerSampleApp::DisplayFloatingCtrlInfoText(const PointerEvent& e) - { - ScenePoint2D p = e.GetMainPosition().Apply(controller_->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 (controller_->GetScene().HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)) - { - TextSceneLayer& layer = - dynamic_cast(controller_->GetScene().GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); - layer.SetText(buf); - layer.SetPosition(p.GetX(), p.GetY()); - } - else - { - std::unique_ptr layer(new TextSceneLayer); - layer->SetColor(0, 255, 0); - layer->SetText(buf); - layer->SetBorder(20); - layer->SetAnchor(BitmapAnchor_BottomCenter); - layer->SetPosition(p.GetX(), p.GetY()); - controller_->GetScene().SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release()); - } - } - - void TrackerSampleApp::HideInfoText() - { - controller_->GetScene().DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX); - } - - ScenePoint2D TrackerSampleApp::GetRandomPointInScene() const - { - unsigned int w = GetCompositor().GetCanvasWidth(); - LOG(TRACE) << "GetCompositor().GetCanvasWidth() = " << - GetCompositor().GetCanvasWidth(); - unsigned int h = GetCompositor().GetCanvasHeight(); - LOG(TRACE) << "GetCompositor().GetCanvasHeight() = " << - GetCompositor().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 = controller_->GetViewport().GetPixelCenterCoordinates(x, y); - LOG(TRACE) << "--> p.GetX() = " << p.GetX() << " p.GetY() = " << p.GetY(); - - ScenePoint2D r = p.Apply(controller_->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 cmd = - boost::make_shared( - boost::ref(IObserver::GetBroker()), - controller_, - GetRandomPointInScene()); - cmd->SetEnd(GetRandomPointInScene()); - controller_->PushCommand(cmd); - } - break; - case 1: - // angle measure - { - boost::shared_ptr cmd = - boost::make_shared( - boost::ref(IObserver::GetBroker()), - controller_, - GetRandomPointInScene()); - cmd->SetCenter(GetRandomPointInScene()); - cmd->SetSide2End(GetRandomPointInScene()); - controller_->PushCommand(cmd); - } - break; - } - } - - void TrackerSampleApp::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(controller_->GetViewport().GetPixelCenterCoordinates( - event.button.x, event.button.y)); - - DisplayFloatingCtrlInfoText(e); - } - else if (activeTracker_.get() != NULL) - { - HideInfoText(); - //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)"; - if (activeTracker_.get() != NULL) - { - //LOG(TRACE) << "(activeTracker_.get() != NULL)"; - PointerEvent e; - e.AddPosition(controller_->GetViewport().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 - { - HideInfoText(); - - PointerEvent e; - e.AddPosition(controller_->GetViewport().GetPixelCenterCoordinates(event.button.x, event.button.y)); - - ScenePoint2D scenePos = e.GetMainPosition().Apply( - controller_->GetScene().GetCanvasToSceneTransform()); - //auto measureTools = GetController()->HitTestMeasureTools(scenePos); - //LOG(TRACE) << "# of hit tests: " << measureTools.size(); - - // this returns the collection of measuring tools where hit test is true - std::vector > measureTools = controller_->HitTestMeasureTools(scenePos); - - // let's refresh the measuring tools highlighted state - // first let's tag them as "unhighlighted" - controller_->ResetMeasuringToolsHighlight(); - - // then immediately take the first one and ask it to highlight the - // measuring tool UI part that is hot - if (measureTools.size() > 0) - { - measureTools[0]->Highlight(scenePos); - } - } - } - else if (event.type == SDL_MOUSEBUTTONUP) - { - if (activeTracker_) - { - PointerEvent e; - e.AddPosition(controller_->GetViewport().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(controller_->GetViewport().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_m: - CreateRandomMeasureTool(); - break; - case SDLK_s: - controller_->FitContent(GetCompositor().GetCanvasWidth(), - GetCompositor().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", - GetCompositor().GetCanvasWidth(), - GetCompositor().GetCanvasHeight()); - break; - - default: - break; - } - } - } - - - void TrackerSampleApp::OnSceneTransformChanged( - const ViewportController::SceneTransformChanged& message) - { - DisplayInfoText(); - } - - boost::shared_ptr TrackerSampleApp::CreateSuitableTracker( - const SDL_Event & event, - const PointerEvent & e) - { - using namespace Orthanc; - - switch (event.button.button) - { - case SDL_BUTTON_MIDDLE: - return boost::shared_ptr(new PanSceneTracker - (controller_, e)); - - case SDL_BUTTON_RIGHT: - return boost::shared_ptr(new ZoomSceneTracker - (controller_, e, GetCompositor().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 hitTestTracker = TrackerHitTest(e); - - if (hitTestTracker != NULL) - { - //LOG(TRACE) << "hitTestTracker != NULL"; - return hitTestTracker; - } - else - { - switch (currentTool_) - { - case GuiTool_Rotate: - //LOG(TRACE) << "Creating RotateSceneTracker"; - return boost::shared_ptr(new RotateSceneTracker( - controller_, e)); - case GuiTool_Pan: - return boost::shared_ptr(new PanSceneTracker( - controller_, e)); - case GuiTool_Zoom: - return boost::shared_ptr(new ZoomSceneTracker( - controller_, e, GetCompositor().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 GuiTool_LineMeasure: - return boost::shared_ptr(new CreateLineMeasureTracker( - IObserver::GetBroker(), controller_, e)); - case GuiTool_AngleMeasure: - return boost::shared_ptr(new CreateAngleMeasureTracker( - IObserver::GetBroker(), controller_, e)); - case GuiTool_CircleMeasure: - LOG(ERROR) << "Not implemented yet!"; - return boost::shared_ptr(); - case GuiTool_EllipseMeasure: - LOG(ERROR) << "Not implemented yet!"; - return boost::shared_ptr(); - default: - throw OrthancException(ErrorCode_InternalError, "Wrong tool!"); - } - } - } - default: - return boost::shared_ptr(); - } - } - - - TrackerSampleApp::TrackerSampleApp(MessageBroker& broker) : IObserver(broker) - , currentTool_(GuiTool_Rotate) - , undoStack_(new UndoStack) - , viewport_("Hello", 1024, 1024, false) // False means we do NOT let Windows treat this as a legacy application that needs to be scaled - { - controller_ = boost::shared_ptr( - new ViewportController(undoStack_, broker, viewport_)); - - controller_->RegisterObserverCallback( - new Callable - (*this, &TrackerSampleApp::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 TrackerSampleApp::PrepareScene() - { - // Texture of 2x2 size - { - Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); - - uint8_t* p = reinterpret_cast(i.GetRow(0)); - p[0] = 255; - p[1] = 0; - p[2] = 0; - - p[3] = 0; - p[4] = 255; - p[5] = 0; - - p = reinterpret_cast(i.GetRow(1)); - p[0] = 0; - p[1] = 0; - p[2] = 255; - - p[3] = 255; - p[4] = 0; - p[5] = 0; - - controller_->GetScene().SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i)); - - std::unique_ptr l(new ColorTextureSceneLayer(i)); - l->SetOrigin(-3, 2); - l->SetPixelSpacing(1.5, 1); - l->SetAngle(20.0 / 180.0 * M_PI); - controller_->GetScene().SetLayer(TEXTURE_2x2_2_ZINDEX, l.release()); - } - - // Texture of 1x1 size - { - Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); - - uint8_t* p = reinterpret_cast(i.GetRow(0)); - p[0] = 255; - p[1] = 0; - p[2] = 0; - - std::unique_ptr l(new ColorTextureSceneLayer(i)); - l->SetOrigin(-2, 1); - l->SetAngle(20.0 / 180.0 * M_PI); - controller_->GetScene().SetLayer(TEXTURE_1x1_ZINDEX, l.release()); - } - - // Some lines - { - std::unique_ptr layer(new PolylineSceneLayer); - - layer->SetThickness(1); - - 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, 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, 0, 255, 0); - - double dy = 1.01; - chain.clear(); - chain.push_back(ScenePoint2D(-4, -4)); - 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, 0, 0, 255); - - controller_->GetScene().SetLayer(LINESET_1_ZINDEX, layer.release()); - } - - // Some text - { - std::unique_ptr layer(new TextSceneLayer); - layer->SetText("Hello"); - controller_->GetScene().SetLayer(LINESET_2_ZINDEX, layer.release()); - } - } - - - void TrackerSampleApp::DisableTracker() - { - if (activeTracker_) - { - activeTracker_->Cancel(); - activeTracker_.reset(); - } - } - - void TrackerSampleApp::TakeScreenshot(const std::string& target, - unsigned int canvasWidth, - unsigned int canvasHeight) - { - CairoCompositor compositor(controller_->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 TrackerSampleApp::TrackerHitTest(const PointerEvent & e) - { - // std::vector> measureTools_; - ScenePoint2D scenePos = e.GetMainPosition().Apply( - controller_->GetScene().GetCanvasToSceneTransform()); - - std::vector > measureTools = controller_->HitTestMeasureTools(scenePos); - - if (measureTools.size() > 0) - { - return measureTools[0]->CreateEditionTracker(e); - } - return boost::shared_ptr(); - } - - 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; - - ICompositor& TrackerSampleApp::GetCompositor() - { - using namespace Orthanc; - try - { - SdlViewport& viewport = dynamic_cast(viewport_); - return viewport.GetCompositor(); - } - catch (std::bad_cast e) - { - throw OrthancException(ErrorCode_InternalError, "Wrong viewport type!"); - } - } - - const ICompositor& TrackerSampleApp::GetCompositor() const - { - using namespace Orthanc; - try - { - SdlViewport& viewport = const_cast(dynamic_cast(viewport_)); - return viewport.GetCompositor(); - } - catch (std::bad_cast e) - { - throw OrthancException(ErrorCode_InternalError, "Wrong viewport type!"); - } - } - - - void TrackerSampleApp::Run() - { - controller_->FitContent(viewport_.GetCanvasWidth(), viewport_.GetCanvasHeight()); - - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(OpenGLMessageCallback, 0); - - GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, - FONT_SIZE_0, Orthanc::Encoding_Latin1); - GetCompositor().SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT, - FONT_SIZE_1, Orthanc::Encoding_Latin1); - - while (!g_stopApplication) - { - GetCompositor().Refresh(); - - 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); - } - else if (event.type == SDL_KEYDOWN && - event.key.repeat == 0 /* Ignore key bounce */) - { - switch (event.key.keysym.sym) - { - case SDLK_f: - viewport_.GetWindow().ToggleMaximize(); - break; - - case SDLK_q: - g_stopApplication = true; - break; - default: - break; - } - } - HandleApplicationEvent(event); - } - SDL_Delay(1); - } - } - - void TrackerSampleApp::SetInfoDisplayMessage( - std::string key, std::string value) - { - if (value == "") - infoTextMap_.erase(key); - else - infoTextMap_[key] = value; - DisplayInfoText(); - } - -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/TrackerSampleApp.h --- a/Samples/Sdl/TrackerSampleApp.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#include "../../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 "../../Framework/Viewport/SdlViewport.h" - -#include - -#include -#include -#include - -namespace OrthancStone -{ - enum GuiTool - { - GuiTool_Rotate = 0, - GuiTool_Pan, - GuiTool_Zoom, - GuiTool_LineMeasure, - GuiTool_CircleMeasure, - GuiTool_AngleMeasure, - GuiTool_EllipseMeasure, - GuiTool_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; - - class TrackerSampleApp : public IObserver - , public boost::enable_shared_from_this - { - public: - // 12 because. - TrackerSampleApp(MessageBroker& broker); - void PrepareScene(); - void Run(); - void SetInfoDisplayMessage(std::string key, std::string value); - void DisableTracker(); - - 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); - - private: - void SelectNextTool(); - void CreateRandomMeasureTool(); - - - /** - In the case of this app, the viewport is an SDL viewport and it has - a OpenGLCompositor& GetCompositor() method - */ - ICompositor& GetCompositor(); - - /** - See the other overload - */ - const ICompositor& GetCompositor() const; - - /** - This returns a random point in the canvas part of the scene, but in - scene coordinates - */ - ScenePoint2D GetRandomPointInScene() const; - - boost::shared_ptr TrackerHitTest(const PointerEvent& e); - - boost::shared_ptr 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 cmd); - void Undo(); - void Redo(); - - private: - void DisplayFloatingCtrlInfoText(const PointerEvent& e); - void DisplayInfoText(); - void HideInfoText(); - - private: - /** - 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 controller_; - - std::map infoTextMap_; - boost::shared_ptr 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; - - GuiTool currentTool_; - boost::shared_ptr undoStack_; - SdlOpenGLViewport viewport_; - }; - -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/Sdl/cpp.hint --- a/Samples/Sdl/cpp.hint Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -#define ORTHANC_OVERRIDE -#define ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(FILE, LINE, NAME) class NAME : public ::OrthancStone::IMessage {}; diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/BasicMPR.cpp --- a/Samples/WebAssembly/BasicMPR.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,427 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - - -#include "dev.h" - -#include - -#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::unique_ptr source_; - VolumeProjection projection_; - std::vector 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& 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 - (*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(currentPlane_) + delta; - unsigned int next; - - if (tmp < 0) - { - next = 0; - } - else if (tmp >= static_cast(planes_.size())) - { - next = planes_.size() - 1; - } - else - { - next = static_cast(tmp); - } - - if (next != currentPlane_) - { - currentPlane_ = next; - Refresh(); - } - } - } - }; -} - - - - -boost::shared_ptr ct_(new OrthancStone::DicomVolumeImage); - -boost::shared_ptr loader_; - -std::unique_ptr widget1_; -std::unique_ptr widget2_; -std::unique_ptr 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(userData); - - if (ctrlDown_) - { - delta *= static_cast(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 - (*this, &TestSleep::Handle)); - - LOG(INFO) << "STARTING"; - Schedule(); - } - }; - - //static TestSleep testSleep(broker_, oracle_); -} - - - -static std::map arguments_; - -static bool GetArgument(std::string& value, - const std::string& key) -{ - std::map::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::unique_ptr 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::unique_ptr 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::unique_ptr 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(); - } - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/BasicMPR.html --- a/Samples/WebAssembly/BasicMPR.html Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ - - - - - - - - - - - - Stone of Orthanc - - - - - - - - - - - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/BasicScene.cpp --- a/Samples/WebAssembly/BasicScene.cpp Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,210 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#include "dev.h" - -#include -#include - -// From Stone -#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" -#include "../../Framework/StoneInitialization.h" - -// From Orthanc framework -#include -#include -#include - -void PrepareScene(OrthancStone::Scene2D& scene) -{ - using namespace OrthancStone; - - // Texture of 2x2 size - if (1) - { - Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); - - uint8_t *p = reinterpret_cast(i.GetRow(0)); - p[0] = 255; - p[1] = 0; - p[2] = 0; - - p[3] = 0; - p[4] = 255; - p[5] = 0; - - p = reinterpret_cast(i.GetRow(1)); - p[0] = 0; - p[1] = 0; - p[2] = 255; - - p[3] = 255; - p[4] = 0; - p[5] = 0; - - scene.SetLayer(12, new ColorTextureSceneLayer(i)); - - std::unique_ptr l(new ColorTextureSceneLayer(i)); - l->SetOrigin(-3, 2); - l->SetPixelSpacing(1.5, 1); - l->SetAngle(20.0 / 180.0 * M_PI); - scene.SetLayer(14, l.release()); - } - - // Texture of 1x1 size - if (1) - { - Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false); - - uint8_t *p = reinterpret_cast(i.GetRow(0)); - p[0] = 255; - p[1] = 0; - p[2] = 0; - - std::unique_ptr l(new ColorTextureSceneLayer(i)); - l->SetOrigin(-2, 1); - l->SetAngle(20.0 / 180.0 * M_PI); - scene.SetLayer(13, l.release()); - } - - // Some lines - if (1) - { - std::unique_ptr layer(new PolylineSceneLayer); - - layer->SetThickness(1); - - 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, 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, 0, 255, 0); - - double dy = 1.01; - chain.clear(); - chain.push_back(ScenePoint2D(-4, -4)); - 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, 0, 0, 255); - - scene.SetLayer(50, layer.release()); - } - - // Some text - if (1) - { - std::unique_ptr layer(new TextSceneLayer); - layer->SetText("Hello"); - scene.SetLayer(100, layer.release()); - } -} - - -std::unique_ptr viewport1_; -std::unique_ptr viewport2_; -std::unique_ptr viewport3_; -boost::shared_ptr controller1_; -boost::shared_ptr controller2_; -boost::shared_ptr controller3_; -OrthancStone::MessageBroker broker_; - - -EM_BOOL OnWindowResize( - int eventType, const EmscriptenUiEvent *uiEvent, void *userData) -{ - if (viewport1_.get() != NULL) - { - viewport1_->UpdateSize(); - } - - if (viewport2_.get() != NULL) - { - viewport2_->UpdateSize(); - } - - if (viewport3_.get() != NULL) - { - viewport3_->UpdateSize(); - } - - 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("mycanvas1")); - PrepareScene(viewport1_->GetScene()); - viewport1_->UpdateSize(); - - viewport2_.reset(new OrthancStone::WebAssemblyViewport("mycanvas2")); - PrepareScene(viewport2_->GetScene()); - viewport2_->UpdateSize(); - - viewport3_.reset(new OrthancStone::WebAssemblyViewport("mycanvas3")); - PrepareScene(viewport3_->GetScene()); - viewport3_->UpdateSize(); - - viewport1_->GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, - FONT_SIZE, Orthanc::Encoding_Latin1); - viewport2_->GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, - FONT_SIZE, Orthanc::Encoding_Latin1); - viewport3_->GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, - FONT_SIZE, Orthanc::Encoding_Latin1); - - controller1_.reset(new OrthancStone::ViewportController(boost::make_shared(), broker_, *viewport1_)); - controller2_.reset(new OrthancStone::ViewportController(boost::make_shared(), broker_, *viewport2_)); - controller3_.reset(new OrthancStone::ViewportController(boost::make_shared(), broker_, *viewport3_)); - - controller1_->FitContent(viewport1_->GetCanvasWidth(), viewport1_->GetCanvasHeight()); - controller2_->FitContent(viewport2_->GetCanvasWidth(), viewport2_->GetCanvasHeight()); - controller3_->FitContent(viewport3_->GetCanvasWidth(), viewport3_->GetCanvasHeight()); - - viewport1_->Refresh(); - viewport2_->Refresh(); - viewport3_->Refresh(); - - SetupEvents("mycanvas1", controller1_); - SetupEvents("mycanvas2", controller2_); - SetupEvents("mycanvas3", controller3_); - - emscripten_set_resize_callback("#window", NULL, false, OnWindowResize); - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/BasicScene.html --- a/Samples/WebAssembly/BasicScene.html Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ - - - - - - - - - - - - Stone of Orthanc - - - - - - - - - - - - - diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/CMakeLists.txt --- a/Samples/WebAssembly/CMakeLists.txt Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) - - -##################################################################### -## Configuration of the Emscripten compiler for WebAssembly target -##################################################################### - -set(WASM_FLAGS "-s WASM=1 -s FETCH=1") - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") -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") - -#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") - - -##################################################################### -## Configuration of the Orthanc framework -##################################################################### - -# This CMake file defines the "ORTHANC_STONE_VERSION" macro, so it -# must be the first inclusion -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/Version.cmake) - -if (ORTHANC_STONE_VERSION STREQUAL "mainline") - set(ORTHANC_FRAMEWORK_VERSION "mainline") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") -else() - set(ORTHANC_FRAMEWORK_VERSION "1.5.7") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") -endif() - -set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") -set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") -set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") - - -##################################################################### -## Configuration of the Stone framework -##################################################################### - -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) -include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake) - -DownloadPackage( - "a24b8136b8f3bb93f166baf97d9328de" - "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" - "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") - -set(ORTHANC_STONE_APPLICATION_RESOURCES - UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf - ) - -SET(ENABLE_GOOGLE_TEST OFF) -SET(ENABLE_LOCALE ON) -SET(ORTHANC_SANDBOXED ON) -SET(ENABLE_WASM ON) - -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneConfiguration.cmake) - -add_definitions( - -DORTHANC_ENABLE_LOGGING_PLUGIN=0 - ) - - -##################################################################### -## Build the samples -##################################################################### - -add_library(OrthancStone STATIC - ${ORTHANC_STONE_SOURCES} - ) - - -if (ON) - add_executable(BasicScene - BasicScene.cpp - #${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.h - ${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.cpp - ) - - target_link_libraries(BasicScene OrthancStone) - - install( - TARGETS BasicScene - RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} - ) -endif() - - -if (ON) - add_executable(BasicMPR - BasicMPR.cpp - ) - - 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} - ) diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/Configuration.json --- a/Samples/WebAssembly/Configuration.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -{ - "Plugins": [ - "/usr/local/share/orthanc/plugins/libOrthancWebViewer.so", - "/usr/local/share/orthanc/plugins/libServeFolders.so" - ], - "StorageDirectory" : "/var/lib/orthanc/db", - "IndexDirectory" : "/var/lib/orthanc/db", - "RemoteAccessAllowed" : true, - "AuthenticationEnabled" : false, - "ServeFolders" : { - "AllowCache" : false, - "GenerateETag" : true, - "Folders" : { - "/stone" : "/root/stone" - } - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/ConfigurationLocalSJO.json --- a/Samples/WebAssembly/ConfigurationLocalSJO.json Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -{ - "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" - } -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/NOTES.txt --- a/Samples/WebAssembly/NOTES.txt Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -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 -$ 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 - - -Local AM -======== - -. ~/apps/emsdk/emsdk_env.sh -cd /mnt/c/o/ -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=${EMSDK}/fastcomp/emscripten/cmake/Modules/Platform/Emscripten.cmake -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT=/mnt/c/o/orthanc/ -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON /mnt/c/o/orthanc-stone/Samples/WebAssembly -DCMAKE_INSTALL_PREFIX=/mnt/c/o/build_install_stone_newsamples_wasm_wsl -ninja diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 2.8.3) + +# Configuration of the Emscripten compiler for WebAssembly target +# --------------------------------------------------------------- +set(USE_WASM ON CACHE BOOL "") +set(ORTHANC_FRAMEWORK_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../orthanc CACHE STRING "") +set(STONE_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../) + +set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") + +set(WASM_FLAGS "-s WASM=1 -s FETCH=1") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") +endif() + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +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 ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") +add_definitions( + -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 +) + +# Stone of Orthanc configuration +# --------------------------------------------------------------- +set(ALLOW_DOWNLOADS ON) +set(ORTHANC_FRAMEWORK_SOURCE "path") + +include(${STONE_ROOT}/Resources/CMake/OrthancStoneParameters.cmake) + +SET(ENABLE_DCMTK ON) +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ENABLE_WASM ON) +SET(ORTHANC_SANDBOXED ON) + +# this will set up the build system for Stone of Orthanc and will +# populate the ORTHANC_STONE_SOURCES CMake variable +include(${STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) + +include_directories(${STONE_ROOT}) + +# Define the WASM module +# --------------------------------------------------------------- +add_executable(SingleFrameViewerWasm + SingleFrameViewer.cpp + ${ORTHANC_STONE_SOURCES} + ) + +# Declare installation files for the module +# --------------------------------------------------------------- +install( + TARGETS SingleFrameViewerWasm + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} + ) + +# Declare installation files for the companion files (web scaffolding) +# please note that ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.js +# (the generated JS loader for the WASM module) is handled by the `install1` +# section above +# --------------------------------------------------------------- +install( + FILES + ${CMAKE_SOURCE_DIR}/SingleFrameViewerApp.js + ${CMAKE_SOURCE_DIR}/index.html + ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.wasm + ${CMAKE_SOURCE_DIR}/WasmWrapper.js + DESTINATION ${CMAKE_INSTALL_PREFIX} + ) diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/SingleFrameViewer/CMakeSettings.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/CMakeSettings.json Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,39 @@ +{ + "configurations": [ + { + "name": "wasm32-RelWithDebInfo", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + //"inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "cmakeToolchain": "C:/osi/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", + "intelliSenseMode": "windows-clang-x64", + "variables": [ + { + "name": "CMAKE_BUILD_TYPE", + "value": "RelWithDebInfo", + "type": "STRING" + }, + { + "name": "ALLOW_DOWNLOADS", + "value": "True", + "type": "BOOL" + }, + { + "name": "STATIC_BUILD", + "value": "True", + "type": "BOOL" + }, + { + "name": "OPENSSL_NO_CAPIENG", + "value": "True", + "type": "BOOL" + } + ] + } + ] +} \ No newline at end of file diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,209 @@ +#include "SingleFrameViewerApplication.h" + +#include + +#include +#include + +#include + +#include +#include + + +#define DISPATCH_JAVASCRIPT_EVENT(name) \ + EM_ASM( \ + const customEvent = document.createEvent("CustomEvent"); \ + customEvent.initCustomEvent(name, false, false, undefined); \ + window.dispatchEvent(customEvent); \ + ); + +#define EXTERN_CATCH_EXCEPTIONS \ + catch (Orthanc::OrthancException& e) \ + { \ + LOG(ERROR) << "OrthancException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (OrthancStone::StoneException& e) \ + { \ + LOG(ERROR) << "StoneException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (std::exception& e) \ + { \ + LOG(ERROR) << "Runtime error: " << e.what(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (...) \ + { \ + LOG(ERROR) << "Native exception"; \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } + + + +namespace OrthancStone +{ + class Observer : public IWebViewerLoadersObserver + { + public: + virtual void SignalSeriesUpdated(LoadedDicomResources& series) + { + LOG(INFO) << "====================================="; + LOG(INFO) << series.GetSize() << " series"; + + /*for (size_t i = 0; i < series.GetSize(); i++) + { + series.GetResource(i).Print(stdout); + printf("\n"); + }*/ + } + + virtual void SignalThumbnailLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + SeriesThumbnailType type) + { + LOG(INFO) << "*** Thumbnail loaded: " << studyInstanceUid << " / " + << seriesInstanceUid << " (type " << type << ")"; + } + }; +} + + + +static std::auto_ptr context_; +static boost::shared_ptr application_; + + +extern "C" +{ + int main(int argc, char const *argv[]) + { + try + { + Orthanc::Logging::Initialize(); + Orthanc::Logging::EnableInfoLevel(true); + //Orthanc::Logging::EnableTraceLevel(true); + LOG(WARNING) << "Initializing native Stone"; + + LOG(WARNING) << "Compiled with Emscripten " << __EMSCRIPTEN_major__ + << "." << __EMSCRIPTEN_minor__ + << "." << __EMSCRIPTEN_tiny__; + + LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness()); + context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1)); + context_->SetLocalOrthanc(".."); + context_->SetDicomCacheSize(128 * 1024 * 1024); // 128MB + + DISPATCH_JAVASCRIPT_EVENT("StoneInitialized"); + } + EXTERN_CATCH_EXCEPTIONS; + + return 0; + } + + + EMSCRIPTEN_KEEPALIVE + void InitializeViewport(const char* canvasId) + { + try + { + if (context_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "The loaders context is not available yet"); + } + + if (application_.get() != NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "Only one single viewport is available for this application"); + } + + { + std::auto_ptr observer(new OrthancStone::Observer); + +#if 1 + OrthancStone::DicomSource source1; + //source1.SetDicomWebSource("http://localhost:8042/dicom-web"); source1.SetDicomWebRendered(true); + source1.SetDicomWebThroughOrthancSource("self"); source1.SetDicomWebRendered(true); + //source1.SetDicomWebThroughOrthancSource("my-google"); source1.SetDicomWebRendered(false); + boost::shared_ptr app2( + OrthancStone::WebViewerLoaders::Create(*context_, source1, true, observer.release())); +#else + OrthancStone::DicomSource source1; + source1.SetOrthancSource(); + boost::shared_ptr app2( + OrthancStone::WebViewerLoaders::Create(*context_, source1, true, observer.release())); + //app2->AddOrthancStudy("27f7126f-4f66fb14-03f4081b-f9341db2-53925988"); + //app2->AddOrthancSeries("1e2c125c-411b8e86-3f4fe68e-a7584dd3-c6da78f0"); +#endif + + // BRAINIX + //app2->AddDicomAllSeries(); + //app2->AddDicomStudy("2.16.840.1.113669.632.20.1211.10000357775"); + app2->AddDicomSeries("2.16.840.1.113669.632.20.1211.10000357775", "1.3.46.670589.11.0.0.11.4.2.0.8743.5.3800.2006120117110979000"); // Standard image: type 5 + + app2->AddDicomStudy("1.3.51.0.7.633920140505.6339234439.633987.633918098"); // "Video" type 4: video720p.dcm + app2->AddDicomStudy("1.2.276.0.7230010.3.1.2.2344313775.14992.1458058404.7528"); // "PDF" type 3: pdf.dcm + app2->AddDicomSeries("1.2.276.0.7230010.3.1.2.296485376.1.1568899779.944131", "1.2.276.0.7230010.3.1.3.296485376.1.1568899781.944588"); // RTSTRUCT, "Unsupported" type 2: DICOM/WebViewer2/TFE/IMAGES/IM452 + + + //app2->AddDicomStudy("1.2.276.0.7230010.3.1.2.380371456.1.1544616291.954997"); // CSPO + } + + boost::shared_ptr viewport(OrthancStone::GetWebGLViewportsRegistry().Add(canvasId)); + application_ = OrthancStone::Application::Create(*context_, viewport); + + // Paint the viewport to black + + { + OrthancStone::WebGLViewportsRegistry::Accessor accessor( + OrthancStone::GetWebGLViewportsRegistry(), canvasId); + + if (accessor.IsValid()) + { + accessor.GetViewport().Invalidate(); + } + } + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void LoadOrthanc(const char* instance, + int frame) + { + try + { + if (application_.get() != NULL) + { + OrthancStone::DicomSource source; + application_->LoadOrthancFrame(source, instance, frame); + } + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void LoadDicomWeb(const char* server, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid, + int frame) + { + try + { + if (application_.get() != NULL) + { + OrthancStone::DicomSource source; + source.SetDicomWebThroughOrthancSource(server); + application_->LoadDicomWebFrame(source, studyInstanceUid, seriesInstanceUid, + sopInstanceUid, frame); + } + } + EXTERN_CATCH_EXCEPTIONS; + } +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,89 @@ + +// This object wraps the functions exposed by the wasm module + +const WasmModuleWrapper = function() { + this._InitializeViewport = undefined; + this._LoadOrthanc = undefined; + this._LoadDicomWeb = undefined; +}; + +WasmModuleWrapper.prototype.Setup = function(Module) { + this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]); + this._LoadOrthanc = Module.cwrap('LoadOrthanc', null, [ 'string', 'int' ]); + this._LoadDicomWeb = Module.cwrap('LoadDicomWeb', null, [ 'string', 'string', 'string', 'string', 'int' ]); +}; + +WasmModuleWrapper.prototype.InitializeViewport = function(canvasId) { + this._InitializeViewport(canvasId); +}; + +WasmModuleWrapper.prototype.LoadOrthanc = function(instance, frame) { + this._LoadOrthanc(instance, frame); +}; + +WasmModuleWrapper.prototype.LoadDicomWeb = function(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) { + this._LoadDicomWeb(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame); +}; + +var moduleWrapper = new WasmModuleWrapper(); + +$(document).ready(function() { + + window.addEventListener('StoneInitialized', function() { + stone.Setup(Module); + console.warn('Native Stone properly intialized'); + + stone.InitializeViewport('viewport'); + }); + + window.addEventListener('StoneException', function() { + alert('Exception caught in Stone'); + }); + + var scriptSource; + + if ('WebAssembly' in window) { + console.warn('Loading WebAssembly'); + scriptSource = 'SingleFrameViewerWasm.js'; + } else { + console.error('Your browser does not support WebAssembly!'); + } + + // Option 1: Loading script using plain HTML + + /* + var script = document.createElement('script'); + script.src = scriptSource; + script.type = 'text/javascript'; + document.body.appendChild(script); + */ + + // Option 2: Loading script using AJAX (gives the opportunity to + // report explicit errors) + + axios.get(scriptSource) + .then(function (response) { + var script = document.createElement('script'); + script.innerHTML = response.data; + script.type = 'text/javascript'; + document.body.appendChild(script); + }) + .catch(function (error) { + alert('Cannot load the WebAssembly framework'); + }); +}); + + +$('#orthancLoad').click(function() { + stone.LoadOrthanc($('#orthancInstance').val(), + $('#orthancFrame').val()); +}); + + +$('#dicomWebLoad').click(function() { + stone.LoadDicomWeb($('#dicomWebServer').val(), + $('#dicomWebStudy').val(), + $('#dicomWebSeries').val(), + $('#dicomWebInstance').val(), + $('#dicomWebFrame').val()); +}); diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,483 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + + +namespace OrthancStone +{ + class Application : public ObserverBase + { + private: + ILoadersContext& context_; + boost::shared_ptr viewport_; + boost::shared_ptr dicomLoader_; + boost::shared_ptr framesLoader_; + + Application(ILoadersContext& context, + boost::shared_ptr viewport) : + context_(context), + viewport_(viewport) + { + } + + void Handle(const SeriesFramesLoader::FrameLoadedMessage& message) + { + LOG(INFO) << "Frame decoded! " + << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight() + << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat()); + + std::auto_ptr layer( + message.GetInstanceParameters().CreateTexture(message.GetImage())); + layer->SetLinearInterpolation(true); + + { + std::auto_ptr lock(viewport_->Lock()); + lock->GetController().GetScene().SetLayer(0, layer.release()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + } + + void Handle(const DicomResourcesLoader::SuccessMessage& message) + { + if (message.GetResources()->GetSize() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + //message.GetResources()->GetResource(0).Print(stdout); + + { + std::auto_ptr lock(context_.Lock()); + SeriesFramesLoader::Factory f(*message.GetResources()); + + framesLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + Register(*framesLoader_, &Application::Handle); + + assert(message.HasUserPayload()); + const Orthanc::SingleValueObject& payload = + dynamic_cast&>(message.GetUserPayload()); + + LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue(); + framesLoader_->ScheduleLoadFrame( + 0, message.GetDicomSource(), payload.GetValue(), + message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */, + NULL); + } + } + + public: + static boost::shared_ptr Create(ILoadersContext& context, + boost::shared_ptr viewport) + { + boost::shared_ptr application(new Application(context, viewport)); + + { + std::auto_ptr lock(context.Lock()); + DicomResourcesLoader::Factory f; + application->dicomLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + } + + application->Register(*application->dicomLoader_, &Application::Handle); + + return application; + } + + void LoadOrthancFrame(const DicomSource& source, + const std::string& instanceId, + unsigned int frame) + { + std::auto_ptr lock(context_.Lock()); + + dicomLoader_->ScheduleLoadOrthancResource( + boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), + 0, source, Orthanc::ResourceType_Instance, instanceId, + new Orthanc::SingleValueObject(frame)); + } + + void LoadDicomWebFrame(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + unsigned int frame) + { + std::auto_ptr lock(context_.Lock()); + + // We first must load the "/metadata" to know the number of frames + dicomLoader_->ScheduleGetDicomWeb( + boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, + "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", + new Orthanc::SingleValueObject(frame)); + } + + void FitContent() + { + std::auto_ptr lock(viewport_->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + }; + + + + class IWebViewerLoadersObserver : public boost::noncopyable + { + public: + virtual ~IWebViewerLoadersObserver() + { + } + + virtual void SignalSeriesUpdated(LoadedDicomResources& series) = 0; + + virtual void SignalThumbnailLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + SeriesThumbnailType type) = 0; + }; + + + class WebViewerLoaders : public ObserverBase + { + private: + static const int PRIORITY_ADD_RESOURCES = 0; + static const int PRIORITY_THUMBNAILS = OracleScheduler::PRIORITY_LOW + 100; + + enum Type + { + Type_Orthanc = 1, + Type_DicomWeb = 2 + }; + + ILoadersContext& context_; + std::auto_ptr observer_; + bool loadThumbnails_; + DicomSource source_; + std::set scheduledSeries_; + std::set scheduledThumbnails_; + std::set scheduledStudies_; + boost::shared_ptr loadedSeries_; + boost::shared_ptr loadedStudies_; + boost::shared_ptr resourcesLoader_; + boost::shared_ptr thumbnailsLoader_; + + WebViewerLoaders(ILoadersContext& context, + IWebViewerLoadersObserver* observer) : + context_(context), + observer_(observer), + loadThumbnails_(false) + { + loadedSeries_ = boost::make_shared(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); + loadedStudies_ = boost::make_shared(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID); + } + + static Orthanc::IDynamicObject* CreatePayload(Type type) + { + return new Orthanc::SingleValueObject(type); + } + + void HandleThumbnail(const SeriesThumbnailsLoader::ThumbnailLoadedMessage& message) + { + if (observer_.get() != NULL) + { + observer_->SignalThumbnailLoaded(message.GetStudyInstanceUid(), + message.GetSeriesInstanceUid(), + message.GetType()); + } + } + + void HandleLoadedResources(const DicomResourcesLoader::SuccessMessage& message) + { + LoadedDicomResources series(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); + + switch (dynamic_cast&>(message.GetUserPayload()).GetValue()) + { + case Type_DicomWeb: + { + for (size_t i = 0; i < loadedSeries_->GetSize(); i++) + { + std::string study; + if (loadedSeries_->GetResource(i).LookupStringValue( + study, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && + loadedStudies_->HasResource(study)) + { + Orthanc::DicomMap m; + m.Assign(loadedSeries_->GetResource(i)); + loadedStudies_->MergeResource(m, study); + series.AddResource(m); + } + } + + break; + } + + case Type_Orthanc: + { + for (size_t i = 0; i < message.GetResources()->GetSize(); i++) + { + series.AddResource(message.GetResources()->GetResource(i)); + } + + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + if (loadThumbnails_ && + (!source_.IsDicomWeb() || + source_.HasDicomWebRendered())) + { + for (size_t i = 0; i < series.GetSize(); i++) + { + std::string patientId, studyInstanceUid, seriesInstanceUid; + if (series.GetResource(i).LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) && + series.GetResource(i).LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && + series.GetResource(i).LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) && + scheduledThumbnails_.find(seriesInstanceUid) == scheduledThumbnails_.end()) + { + scheduledThumbnails_.insert(seriesInstanceUid); + thumbnailsLoader_->ScheduleLoadThumbnail(source_, patientId, studyInstanceUid, seriesInstanceUid); + } + } + } + + if (observer_.get() != NULL && + series.GetSize() > 0) + { + observer_->SignalSeriesUpdated(series); + } + } + + void HandleOrthancRestApi(const OrthancRestApiCommand::SuccessMessage& message) + { + Json::Value body; + message.ParseJsonBody(body); + + if (body.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + else + { + for (Json::Value::ArrayIndex i = 0; i < body.size(); i++) + { + if (body[i].type() == Json::stringValue) + { + AddOrthancSeries(body[i].asString()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + } + } + + public: + static boost::shared_ptr Create(ILoadersContext& context, + const DicomSource& source, + bool loadThumbnails, + IWebViewerLoadersObserver* observer) + { + boost::shared_ptr application(new WebViewerLoaders(context, observer)); + application->source_ = source; + application->loadThumbnails_ = loadThumbnails; + + { + std::auto_ptr lock(context.Lock()); + + { + DicomResourcesLoader::Factory f; + application->resourcesLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + } + + { + SeriesThumbnailsLoader::Factory f; + f.SetPriority(PRIORITY_THUMBNAILS); + application->thumbnailsLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + } + + application->Register( + lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi); + + application->Register( + *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources); + + application->Register( + *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail); + + lock->AddLoader(application); + } + + return application; + } + + void AddDicomAllSeries() + { + std::auto_ptr lock(context_.Lock()); + + if (source_.IsDicomWeb()) + { + resourcesLoader_->ScheduleGetDicomWeb(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + "/series", CreatePayload(Type_DicomWeb)); + resourcesLoader_->ScheduleGetDicomWeb(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, + "/studies", CreatePayload(Type_DicomWeb)); + } + else if (source_.IsOrthanc()) + { + std::auto_ptr command(new OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Get); + command->SetUri("/series"); + lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + void AddDicomStudy(const std::string& studyInstanceUid) + { + // Avoid adding twice the same study + if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end()) + { + scheduledStudies_.insert(studyInstanceUid); + + if (source_.IsDicomWeb()) + { + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + + std::set tags; + + { + std::auto_ptr lock(context_.Lock()); + + resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb)); + + resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb)); + } + } + else if (source_.IsOrthanc()) + { + std::auto_ptr command(new OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetUri("/tools/find"); + + Json::Value body; + body["Level"] = "Series"; + body["Query"] = Json::objectValue; + body["Query"]["StudyInstanceUID"] = studyInstanceUid; + command->SetBody(body); + + { + std::auto_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + + void AddDicomSeries(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) + { + std::set tags; + + std::auto_ptr lock(context_.Lock()); + + if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end()) + { + scheduledStudies_.insert(studyInstanceUid); + + if (source_.IsDicomWeb()) + { + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + + resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb)); + } + } + + if (scheduledSeries_.find(seriesInstanceUid) == scheduledSeries_.end()) + { + scheduledSeries_.insert(seriesInstanceUid); + + if (source_.IsDicomWeb()) + { + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false); + + resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb)); + } + else if (source_.IsOrthanc()) + { + std::auto_ptr command(new OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetUri("/tools/find"); + + Json::Value body; + body["Level"] = "Series"; + body["Query"] = Json::objectValue; + body["Query"]["StudyInstanceUID"] = studyInstanceUid; + body["Query"]["SeriesInstanceUID"] = seriesInstanceUid; + command->SetBody(body); + + lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + + void AddOrthancStudy(const std::string& orthancId) + { + if (source_.IsOrthanc()) + { + std::auto_ptr lock(context_.Lock()); + resourcesLoader_->ScheduleLoadOrthancResources( + loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Study, orthancId, Orthanc::ResourceType_Series, + CreatePayload(Type_Orthanc)); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, + "Only applicable to Orthanc DICOM sources"); + } + } + + void AddOrthancSeries(const std::string& orthancId) + { + if (source_.IsOrthanc()) + { + std::auto_ptr lock(context_.Lock()); + resourcesLoader_->ScheduleLoadOrthancResource( + loadedSeries_, PRIORITY_ADD_RESOURCES, + source_, Orthanc::ResourceType_Series, orthancId, + CreatePayload(Type_Orthanc)); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, + "Only applicable to Orthanc DICOM sources"); + } + } + }; +} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/SingleFrameViewer/WasmWrapper.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/WasmWrapper.js Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,27 @@ + +const Stone = function() { + this._InitializeViewport = undefined; + this._LoadOrthanc = undefined; + this._LoadDicomWeb = undefined; +}; + +Stone.prototype.Setup = function(Module) { + this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]); + this._LoadOrthanc = Module.cwrap('LoadOrthanc', null, [ 'string', 'int' ]); + this._LoadDicomWeb = Module.cwrap('LoadDicomWeb', null, [ 'string', 'string', 'string', 'string', 'int' ]); +}; + +Stone.prototype.InitializeViewport = function(canvasId) { + this._InitializeViewport(canvasId); +}; + +Stone.prototype.LoadOrthanc = function(instance, frame) { + this._LoadOrthanc(instance, frame); +}; + +Stone.prototype.LoadDicomWeb = function(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) { + this._LoadDicomWeb(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame); +}; + +var stone = new Stone(); + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/SingleFrameViewer/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/index.html Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,71 @@ + + + + Osimis' Web Viewer + + + + + + + + + + +

Viewport

+ + + + +

Load from Orthanc

+

+ Orthanc instance: + + +

+

+ Frame number: +

+

+ +

+ +

Load from DICOMweb

+

+ Server name in Orthanc: +

+

+ Study instance UID: +

+

+ Series instance UID: +

+

+ SOP instance UID: +

+

+ Frame number: +

+

+ +

+ + + + + + + + diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/app.js --- a/Samples/WebAssembly/app.js Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -/** - * 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); - }); -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/dev.h --- a/Samples/WebAssembly/dev.h Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,202 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../../Framework/Viewport/WebAssemblyViewport.h" -#include "../../Framework/Scene2D/OpenGLCompositor.h" -#include "../../Framework/Scene2D/PanSceneTracker.h" -#include "../../Framework/Scene2D/RotateSceneTracker.h" -#include "../../Framework/Scene2D/ZoomSceneTracker.h" -#include "../../Framework/Scene2DViewport/UndoStack.h" -#include "../../Framework/Scene2DViewport/ViewportController.h" - -#include - -#include -#include - -static const unsigned int FONT_SIZE = 32; - -namespace OrthancStone -{ - class ActiveTracker : public boost::noncopyable - { - private: - boost::shared_ptr tracker_; - std::string canvasIdentifier_; - bool insideCanvas_; - - public: - ActiveTracker(const boost::shared_ptr& tracker, - const std::string& canvasId) : - tracker_(tracker), - canvasIdentifier_(canvasId), - 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::IViewport& viewport) -{ - std::unique_ptr 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::unique_ptr tracker_; - -EM_BOOL OnMouseEvent(int eventType, - const EmscriptenMouseEvent *mouseEvent, - void *userData) -{ - if (mouseEvent != NULL && - userData != NULL) - { - boost::shared_ptr& controller = - *reinterpret_cast*>(userData); - - switch (eventType) - { - case EMSCRIPTEN_EVENT_CLICK: - { - static unsigned int count = 0; - char buf[64]; - sprintf(buf, "click %d", count++); - - std::unique_ptr layer(new OrthancStone::TextSceneLayer); - layer->SetText(buf); - controller->GetViewport().GetScene().SetLayer(100, layer.release()); - controller->GetViewport().Refresh(); - break; - } - - case EMSCRIPTEN_EVENT_MOUSEDOWN: - { - boost::shared_ptr t; - - { - std::unique_ptr event( - ConvertMouseEvent(*mouseEvent, controller->GetViewport())); - - switch (mouseEvent->button) - { - case 0: // Left button - emscripten_console_log("Creating RotateSceneTracker"); - t.reset(new OrthancStone::RotateSceneTracker( - controller, *event)); - break; - - case 1: // Middle button - emscripten_console_log("Creating PanSceneTracker"); - LOG(INFO) << "Creating PanSceneTracker" ; - t.reset(new OrthancStone::PanSceneTracker( - controller, *event)); - break; - - case 2: // Right button - emscripten_console_log("Creating ZoomSceneTracker"); - t.reset(new OrthancStone::ZoomSceneTracker( - controller, *event, controller->GetViewport().GetCanvasWidth())); - break; - - default: - break; - } - } - - if (t.get() != NULL) - { - tracker_.reset( - new OrthancStone::ActiveTracker(t, controller->GetViewport().GetCanvasIdentifier())); - controller->GetViewport().Refresh(); - } - - break; - } - - case EMSCRIPTEN_EVENT_MOUSEMOVE: - if (tracker_.get() != NULL) - { - std::unique_ptr event( - ConvertMouseEvent(*mouseEvent, controller->GetViewport())); - tracker_->PointerMove(*event); - controller->GetViewport().Refresh(); - } - break; - - case EMSCRIPTEN_EVENT_MOUSEUP: - if (tracker_.get() != NULL) - { - std::unique_ptr event( - ConvertMouseEvent(*mouseEvent, controller->GetViewport())); - tracker_->PointerUp(*event); - controller->GetViewport().Refresh(); - if (!tracker_->IsAlive()) - tracker_.reset(); - } - break; - - default: - break; - } - } - - return true; -} - - -void SetupEvents(const std::string& canvas, - boost::shared_ptr& controller) -{ - emscripten_set_mousedown_callback(canvas.c_str(), &controller, false, OnMouseEvent); - emscripten_set_mousemove_callback(canvas.c_str(), &controller, false, OnMouseEvent); - emscripten_set_mouseup_callback(canvas.c_str(), &controller, false, OnMouseEvent); -} diff -r 4431ffdcc2a4 -r f4a06ad1580b Samples/WebAssembly/index.html --- a/Samples/WebAssembly/index.html Mon Apr 20 18:26:32 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ - - - - - - - Stone of Orthanc - - -

Available samples

- - - diff -r 4431ffdcc2a4 -r f4a06ad1580b UnitTestsSources/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UnitTestsSources/CMakeLists.txt Wed Apr 22 14:05:47 2020 +0200 @@ -0,0 +1,122 @@ +cmake_minimum_required(VERSION 2.8.3) +project(OrthancStone) + +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() + +set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") +set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") +set(ENABLE_WASM OFF CACHE BOOL "Target WASM application") +set(ENABLE_UNITTESTS ON + + +if (ENABLE_WASM) + set(ORTHANC_SANDBOXED ON) + if (ENABLE_SDL) + message("ENABLE_SDL is only supported in native (incompatible with ENABLE_WASM)") + endif() + if (ENABLE_QT) + message("ENABLE_QT is only supported in native (incompatible with ENABLE_WASM)") + endif() + set(ENABLE_NATIVE OFF) + set(ORTHANC_SANDBOXED OFF) + set(ENABLE_CRYPTO_OPTIONS ON) + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_WEB_CLIENT ON) +elseif (ENABLE_QT OR ENABLE_SDL) + set(ENABLE_NATIVE ON) + set(ORTHANC_SANDBOXED OFF) + set(ENABLE_CRYPTO_OPTIONS ON) + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_WEB_CLIENT ON) +else() + set(ENABLE_NATIVE ON) + set(ENABLE_OPENGL OFF) +endif() + +##################################################################### +## Configuration for Orthanc +##################################################################### + +if (ORTHANC_STONE_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_VERSION "1.4.1") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc source code (can be \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + +add_definitions( + -DORTHANC_ENABLE_LOGGING_PLUGIN=0 + ) + +##################################################################### +## Build a static library containing the Orthanc Stone framework +##################################################################### + +LIST(APPEND ORTHANC_BOOST_COMPONENTS program_options) + +include(../Resources/CMake/OrthancStoneConfiguration.cmake) + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ) + +include_directories(${ORTHANC_STONE_ROOT}) + +##################################################################### +## Build the unit tests +##################################################################### + +if (ENABLE_UNITTESTS) + add_executable(UnitTests + ${GOOGLE_TEST_SOURCES} + ${ORTHANC_STONE_ROOT}/UnitTestsSources/GenericToolboxTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/ImageToolboxTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/PixelTestPatternsTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStrategy.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStructureSet.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp + ) + + target_link_libraries(UnitTests OrthancStone) + + add_custom_command( + TARGET UnitTests + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${ORTHANC_STONE_ROOT}/UnitTestsSources/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" + "$/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" + ) + +endif() + +##################################################################### +## Generate the documentation if Doxygen is present +##################################################################### + +find_package(Doxygen) +if (DOXYGEN_FOUND) + configure_file( + ${ORTHANC_STONE_ROOT}/Resources/OrthancStone.doxygen + ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen + @ONLY) + + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancStone.doxygen + COMMENT "Generating documentation with Doxygen" VERBATIM + ) +else() + message("Doxygen not found. The documentation will not be built.") +endif() diff -r 4431ffdcc2a4 -r f4a06ad1580b UnitTestsSources/GenericToolboxTests.cpp --- a/UnitTestsSources/GenericToolboxTests.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/UnitTestsSources/GenericToolboxTests.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -20,8 +20,9 @@ #include -#include +#include #include +#include #include "gtest/gtest.h" @@ -3878,19 +3879,17 @@ bool ok = true; { - boost::chrono::system_clock::time_point start = boost::chrono::system_clock::now(); + boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); for (size_t i = 0; i < NUM_TIMINGS_CONVS; ++i) { ok = StringToDouble(r, txt); } - boost::chrono::system_clock::time_point end = boost::chrono::system_clock::now(); - boost::chrono::microseconds elapsed = - boost::chrono::duration_cast(end - start); - total_us_StringToDouble += elapsed.count(); + boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); + total_us_StringToDouble += (end - start).total_microseconds(); } { - boost::chrono::system_clock::time_point start = boost::chrono::system_clock::now(); + boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); for (size_t i = 0; i < NUM_TIMINGS_CONVS; ++i) { try @@ -3903,10 +3902,8 @@ ok = false; } } - boost::chrono::system_clock::time_point end = boost::chrono::system_clock::now(); - boost::chrono::microseconds elapsed = - boost::chrono::duration_cast(end - start); - total_us_lexical_cast += elapsed.count(); + boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); + total_us_lexical_cast += (end - start).total_microseconds(); } numConversions += NUM_TIMINGS_CONVS; @@ -4095,19 +4092,17 @@ bool ok = true; { - boost::chrono::system_clock::time_point start = boost::chrono::system_clock::now(); + boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); for (size_t i = 0; i < NUM_TIMINGS_CONVS; ++i) { ok = StringToDouble(r, txt); } - boost::chrono::system_clock::time_point end = boost::chrono::system_clock::now(); - boost::chrono::microseconds elapsed = - boost::chrono::duration_cast(end - start); - total_us_StringToDouble += elapsed.count(); + boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); + total_us_StringToDouble += (end - start).total_microseconds(); } { - boost::chrono::system_clock::time_point start = boost::chrono::system_clock::now(); + boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); for (size_t i = 0; i < NUM_TIMINGS_CONVS; ++i) { try @@ -4120,10 +4115,8 @@ ok = false; } } - boost::chrono::system_clock::time_point end = boost::chrono::system_clock::now(); - boost::chrono::microseconds elapsed = - boost::chrono::duration_cast(end - start); - total_us_lexical_cast += elapsed.count(); + boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); + total_us_lexical_cast += (end - start).total_microseconds(); } numConversions += NUM_TIMINGS_CONVS; @@ -4251,3 +4244,10 @@ EXPECT_EQ(0, green); EXPECT_EQ(0, blue); } + + + + + + + diff -r 4431ffdcc2a4 -r f4a06ad1580b UnitTestsSources/TestMessageBroker.cpp --- a/UnitTestsSources/TestMessageBroker.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/UnitTestsSources/TestMessageBroker.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -21,10 +21,8 @@ #include "gtest/gtest.h" -#include "../Framework/Messages/MessageBroker.h" -#include "../Framework/Messages/IObservable.h" -#include "../Framework/Messages/IObserver.h" -#include "../Framework/Messages/MessageForwarder.h" +#include "Framework/Messages/IObservable.h" +#include "Framework/Messages/ObserverBase.h" int testCounter = 0; @@ -47,51 +45,26 @@ { } }; - - MyObservable(MessageBroker& broker) : - IObservable(broker) - { - } }; - class MyObserver : public IObserver + class MyObserver : public ObserverBase { public: - MyObserver(MessageBroker& broker) - : IObserver(broker) - {} - void HandleCompletedMessage(const MyObservable::MyCustomMessage& message) { testCounter += message.payload_; } - - }; - - - class MyIntermediate : public IObserver, public IObservable - { - IObservable& observedObject_; - public: - MyIntermediate(MessageBroker& broker, IObservable& observedObject) - : IObserver(broker), - IObservable(broker), - observedObject_(observedObject) - { - observedObject_.RegisterObserverCallback(new MessageForwarder(broker, *this)); - } }; } TEST(MessageBroker, TestPermanentConnectionSimpleUseCase) { - MessageBroker broker; - MyObservable observable(broker); - MyObserver observer(broker); + MyObservable observable; + boost::shared_ptr observer(new MyObserver); // create a permanent connection between an observable and an observer - observable.RegisterObserverCallback(new Callable(observer, &MyObserver::HandleCompletedMessage)); + observer->Register(observable, &MyObserver::HandleCompletedMessage); testCounter = 0; observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); @@ -103,155 +76,29 @@ ASSERT_EQ(20, testCounter); // Unregister the observer; make sure it's not called anymore - observable.Unregister(&observer); + observer.reset(); testCounter = 0; observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(0, testCounter); } -TEST(MessageBroker, TestMessageForwarderSimpleUseCase) -{ - MessageBroker broker; - MyObservable observable(broker); - MyIntermediate intermediate(broker, observable); - MyObserver observer(broker); - - // let the observer observers the intermediate that is actually forwarding the messages from the observable - intermediate.RegisterObserverCallback(new Callable(observer, &MyObserver::HandleCompletedMessage)); - - testCounter = 0; - observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); - ASSERT_EQ(12, testCounter); - - // the connection is permanent; if we emit the same message again, the observer will be notified again - testCounter = 0; - observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); - ASSERT_EQ(20, testCounter); -} - TEST(MessageBroker, TestPermanentConnectionDeleteObserver) { - MessageBroker broker; - MyObservable observable(broker); - MyObserver* observer = new MyObserver(broker); + MyObservable observable; + boost::shared_ptr observer(new MyObserver); // create a permanent connection between an observable and an observer - observable.RegisterObserverCallback(new Callable(*observer, &MyObserver::HandleCompletedMessage)); + observer->Register(observable, &MyObserver::HandleCompletedMessage); testCounter = 0; observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); // delete the observer and check that the callback is not called anymore - delete observer; + observer.reset(); // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(0, testCounter); } - -TEST(MessageBroker, TestMessageForwarderDeleteIntermediate) -{ - MessageBroker broker; - MyObservable observable(broker); - MyIntermediate* intermediate = new MyIntermediate(broker, observable); - MyObserver observer(broker); - - // let the observer observers the intermediate that is actually forwarding the messages from the observable - intermediate->RegisterObserverCallback(new Callable(observer, &MyObserver::HandleCompletedMessage)); - - testCounter = 0; - observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); - ASSERT_EQ(12, testCounter); - - delete intermediate; - - observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); - ASSERT_EQ(12, testCounter); -} - -TEST(MessageBroker, TestCustomMessage) -{ - MessageBroker broker; - MyObservable observable(broker); - MyIntermediate intermediate(broker, observable); - MyObserver observer(broker); - - // let the observer observers the intermediate that is actually forwarding the messages from the observable - intermediate.RegisterObserverCallback(new Callable(observer, &MyObserver::HandleCompletedMessage)); - - testCounter = 0; - observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); - ASSERT_EQ(12, testCounter); - - // the connection is permanent; if we emit the same message again, the observer will be notified again - testCounter = 0; - observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); - ASSERT_EQ(20, testCounter); -} - - -#if 0 /* __cplusplus >= 201103L*/ - -TEST(MessageBroker, TestLambdaSimpleUseCase) -{ - MessageBroker broker; - MyObservable observable(broker); - MyObserver* observer = new MyObserver(broker); - - // create a permanent connection between an observable and an observer - observable.RegisterObserverCallback(new LambdaCallable(*observer, [&](const MyObservable::MyCustomMessage& message) {testCounter += 2 * message.payload_;})); - - testCounter = 0; - observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); - ASSERT_EQ(24, testCounter); - - // delete the observer and check that the callback is not called anymore - delete observer; - - // the connection is permanent; if we emit the same message again, the observer will be notified again - testCounter = 0; - observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); - ASSERT_EQ(0, testCounter); -} - -namespace { - class MyObserverWithLambda : public IObserver { - private: - int multiplier_; // this is a private variable we want to access in a lambda - - public: - MyObserverWithLambda(MessageBroker& broker, int multiplier, MyObservable& observable) - : IObserver(broker), - multiplier_(multiplier) - { - // register a callable to a lambda that access private members - observable.RegisterObserverCallback(new LambdaCallable(*this, [this](const MyObservable::MyCustomMessage& message) { - testCounter += multiplier_ * message.payload_; - })); - - } - }; -} - -TEST(MessageBroker, TestLambdaCaptureThisAndAccessPrivateMembers) -{ - MessageBroker broker; - MyObservable observable(broker); - MyObserverWithLambda* observer = new MyObserverWithLambda(broker, 3, observable); - - testCounter = 0; - observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); - ASSERT_EQ(36, testCounter); - - // delete the observer and check that the callback is not called anymore - delete observer; - - // the connection is permanent; if we emit the same message again, the observer will be notified again - testCounter = 0; - observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); - ASSERT_EQ(0, testCounter); -} - -#endif // C++ 11 diff -r 4431ffdcc2a4 -r f4a06ad1580b UnitTestsSources/TestStructureSet.cpp diff -r 4431ffdcc2a4 -r f4a06ad1580b UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Mon Apr 20 18:26:32 2020 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Apr 22 14:05:47 2020 +0200 @@ -25,6 +25,7 @@ #include "../Framework/Deprecated/Toolbox/DownloadStack.h" #include "../Framework/Deprecated/Toolbox/MessagingToolbox.h" #include "../Framework/Deprecated/Toolbox/OrthancSlicesLoader.h" +#include "../Framework/StoneInitialization.h" #include "../Framework/Toolbox/FiniteProjectiveCamera.h" #include "../Framework/Toolbox/GeometryToolbox.h" #include "../Framework/Volumes/ImageBuffer3D.h" @@ -566,20 +567,20 @@ } -static bool IsEqualVector(OrthancStone::Vector a, - OrthancStone::Vector b) +static bool IsEqualRotationVector(OrthancStone::Vector a, + OrthancStone::Vector b) { - if (a.size() == 3 && - b.size() == 3) + if (a.size() != b.size() || + a.size() != 3) + { + return false; + } + else { OrthancStone::LinearAlgebra::NormalizeVector(a); OrthancStone::LinearAlgebra::NormalizeVector(b); return OrthancStone::LinearAlgebra::IsCloseToZero(boost::numeric::ublas::norm_2(a - b)); } - else - { - return false; - } } @@ -593,29 +594,29 @@ OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b); ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r)); - ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b)); + ASSERT_TRUE(IsEqualRotationVector(OrthancStone::LinearAlgebra::Product(r, a), b)); OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, b, a); ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r)); - ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, b), a)); + ASSERT_TRUE(IsEqualRotationVector(OrthancStone::LinearAlgebra::Product(r, b), a)); OrthancStone::LinearAlgebra::AssignVector(a, 1, 0, 0); OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1); OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b); ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r)); - ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b)); + ASSERT_TRUE(IsEqualRotationVector(OrthancStone::LinearAlgebra::Product(r, a), b)); OrthancStone::LinearAlgebra::AssignVector(a, 0, 1, 0); OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1); OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b); ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r)); - ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b)); + ASSERT_TRUE(IsEqualRotationVector(OrthancStone::LinearAlgebra::Product(r, a), b)); OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, 1); OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1); OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b); ASSERT_TRUE(OrthancStone::LinearAlgebra::IsRotationMatrix(r)); - ASSERT_TRUE(IsEqualVector(OrthancStone::LinearAlgebra::Product(r, a), b)); + ASSERT_TRUE(IsEqualRotationVector(OrthancStone::LinearAlgebra::Product(r, a), b)); OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, 0); OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1); @@ -624,11 +625,11 @@ // TODO: Deal with opposite vectors /* - OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, -1); - OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1); - OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b); - OrthancStone::LinearAlgebra::Print(r); - OrthancStone::LinearAlgebra::Print(boost::numeric::ublas::prod(r, a)); + OrthancStone::LinearAlgebra::AssignVector(a, 0, 0, -1); + OrthancStone::LinearAlgebra::AssignVector(b, 0, 0, 1); + OrthancStone::GeometryToolbox::AlignVectorsWithRotation(r, a, b); + OrthancStone::LinearAlgebra::Print(r); + OrthancStone::LinearAlgebra::Print(boost::numeric::ublas::prod(r, a)); */ } @@ -639,13 +640,39 @@ ASSERT_TRUE(Deprecated::MessagingToolbox::ParseJson(response, source.c_str(), source.size())); } + + +static bool IsEqualVectorL1(OrthancStone::Vector a, + OrthancStone::Vector b) +{ + if (a.size() != b.size()) + { + return false; + } + else + { + for (size_t i = 0; i < a.size(); i++) + { + if (!OrthancStone::LinearAlgebra::IsNear(a[i], b[i], 0.0001)) + { + return false; + } + } + + return true; + } +} + + TEST(VolumeImageGeometry, Basic) { - OrthancStone::VolumeImageGeometry g; + using namespace OrthancStone; + + VolumeImageGeometry g; g.SetSizeInVoxels(10, 20, 30); g.SetVoxelDimensions(1, 2, 3); - OrthancStone::Vector p = g.GetCoordinates(0, 0, 0); + Vector p = g.GetCoordinates(0, 0, 0); ASSERT_EQ(3u, p.size()); ASSERT_DOUBLE_EQ(-1.0 / 2.0, p[0]); ASSERT_DOUBLE_EQ(-2.0 / 2.0, p[1]); @@ -656,69 +683,148 @@ ASSERT_DOUBLE_EQ(-2.0 / 2.0 + 20.0 * 2.0, p[1]); ASSERT_DOUBLE_EQ(-3.0 / 2.0 + 30.0 * 3.0, p[2]); - OrthancStone::VolumeProjection proj; + VolumeProjection proj; ASSERT_TRUE(g.DetectProjection(proj, g.GetAxialGeometry().GetNormal())); - ASSERT_EQ(OrthancStone::VolumeProjection_Axial, proj); + ASSERT_EQ(VolumeProjection_Axial, proj); ASSERT_TRUE(g.DetectProjection(proj, g.GetCoronalGeometry().GetNormal())); - ASSERT_EQ(OrthancStone::VolumeProjection_Coronal, proj); + ASSERT_EQ(VolumeProjection_Coronal, proj); ASSERT_TRUE(g.DetectProjection(proj, g.GetSagittalGeometry().GetNormal())); - ASSERT_EQ(OrthancStone::VolumeProjection_Sagittal, proj); + ASSERT_EQ(VolumeProjection_Sagittal, proj); - ASSERT_EQ(10u, g.GetProjectionWidth(OrthancStone::VolumeProjection_Axial)); - ASSERT_EQ(20u, g.GetProjectionHeight(OrthancStone::VolumeProjection_Axial)); - ASSERT_EQ(30u, g.GetProjectionDepth(OrthancStone::VolumeProjection_Axial)); - ASSERT_EQ(10u, g.GetProjectionWidth(OrthancStone::VolumeProjection_Coronal)); - ASSERT_EQ(30u, g.GetProjectionHeight(OrthancStone::VolumeProjection_Coronal)); - ASSERT_EQ(20u, g.GetProjectionDepth(OrthancStone::VolumeProjection_Coronal)); - ASSERT_EQ(20u, g.GetProjectionWidth(OrthancStone::VolumeProjection_Sagittal)); - ASSERT_EQ(30u, g.GetProjectionHeight(OrthancStone::VolumeProjection_Sagittal)); - ASSERT_EQ(10u, g.GetProjectionDepth(OrthancStone::VolumeProjection_Sagittal)); + ASSERT_EQ(10u, g.GetProjectionWidth(VolumeProjection_Axial)); + ASSERT_EQ(20u, g.GetProjectionHeight(VolumeProjection_Axial)); + ASSERT_EQ(30u, g.GetProjectionDepth(VolumeProjection_Axial)); + ASSERT_EQ(10u, g.GetProjectionWidth(VolumeProjection_Coronal)); + ASSERT_EQ(30u, g.GetProjectionHeight(VolumeProjection_Coronal)); + ASSERT_EQ(20u, g.GetProjectionDepth(VolumeProjection_Coronal)); + ASSERT_EQ(20u, g.GetProjectionWidth(VolumeProjection_Sagittal)); + ASSERT_EQ(30u, g.GetProjectionHeight(VolumeProjection_Sagittal)); + ASSERT_EQ(10u, g.GetProjectionDepth(VolumeProjection_Sagittal)); - p = g.GetVoxelDimensions(OrthancStone::VolumeProjection_Axial); + p = g.GetVoxelDimensions(VolumeProjection_Axial); ASSERT_EQ(3u, p.size()); ASSERT_DOUBLE_EQ(1, p[0]); ASSERT_DOUBLE_EQ(2, p[1]); ASSERT_DOUBLE_EQ(3, p[2]); - p = g.GetVoxelDimensions(OrthancStone::VolumeProjection_Coronal); + p = g.GetVoxelDimensions(VolumeProjection_Coronal); ASSERT_EQ(3u, p.size()); ASSERT_DOUBLE_EQ(1, p[0]); ASSERT_DOUBLE_EQ(3, p[1]); ASSERT_DOUBLE_EQ(2, p[2]); - p = g.GetVoxelDimensions(OrthancStone::VolumeProjection_Sagittal); + p = g.GetVoxelDimensions(VolumeProjection_Sagittal); ASSERT_EQ(3u, p.size()); ASSERT_DOUBLE_EQ(2, p[0]); ASSERT_DOUBLE_EQ(3, p[1]); ASSERT_DOUBLE_EQ(1, p[2]); - ASSERT_EQ(0, (int) OrthancStone::VolumeProjection_Axial); - ASSERT_EQ(1, (int) OrthancStone::VolumeProjection_Coronal); - ASSERT_EQ(2, (int) OrthancStone::VolumeProjection_Sagittal); + // Loop over all the voxels of the volume + for (unsigned int z = 0; z < g.GetDepth(); z++) + { + const float zz = (0.5f + static_cast(z)) / static_cast(g.GetDepth()); // Z-center of the voxel + + for (unsigned int y = 0; y < g.GetHeight(); y++) + { + const float yy = (0.5f + static_cast(y)) / static_cast(g.GetHeight()); // Y-center of the voxel + + for (unsigned int x = 0; x < g.GetWidth(); x++) + { + const float xx = (0.5f + static_cast(x)) / static_cast(g.GetWidth()); // X-center of the voxel + + const float sx = 1.0f; + const float sy = 2.0f; + const float sz = 3.0f; + + Vector p = g.GetCoordinates(xx, yy, zz); + + Vector q = (g.GetAxialGeometry().MapSliceToWorldCoordinates( + static_cast(x) * sx, + static_cast(y) * sy) + + z * sz * g.GetAxialGeometry().GetNormal()); + ASSERT_TRUE(IsEqualVectorL1(p, q)); + + q = (g.GetCoronalGeometry().MapSliceToWorldCoordinates( + static_cast(x) * sx, + static_cast(g.GetDepth() - 1 - z) * sz) + + y * sy * g.GetCoronalGeometry().GetNormal()); + ASSERT_TRUE(IsEqualVectorL1(p, q)); + + /** + * WARNING: In sagittal geometry, the normal points to + * REDUCING X-axis in the 3D world. This is necessary to keep + * the right-hand coordinate system. Hence the "-". + **/ + q = (g.GetSagittalGeometry().MapSliceToWorldCoordinates( + static_cast(y) * sy, + static_cast(g.GetDepth() - 1 - z) * sz) + + x * sx * (-g.GetSagittalGeometry().GetNormal())); + ASSERT_TRUE(IsEqualVectorL1(p, q)); + } + } + } + + ASSERT_EQ(0, (int) VolumeProjection_Axial); + ASSERT_EQ(1, (int) VolumeProjection_Coronal); + ASSERT_EQ(2, (int) VolumeProjection_Sagittal); for (int p = 0; p < 3; p++) { - OrthancStone::VolumeProjection projection = (OrthancStone::VolumeProjection) p; - const OrthancStone::CoordinateSystem3D& s = g.GetProjectionGeometry(projection); + VolumeProjection projection = (VolumeProjection) p; + const 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 = g.GetProjectionSlice(projection, i); + CoordinateSystem3D plane = g.GetProjectionSlice(projection, i); - ASSERT_TRUE(IsEqualVector(plane.GetOrigin(), s.GetOrigin() + static_cast(i) * - s.GetNormal() * g.GetVoxelDimensions(projection)[2])); - ASSERT_TRUE(IsEqualVector(plane.GetAxisX(), s.GetAxisX())); - ASSERT_TRUE(IsEqualVector(plane.GetAxisY(), s.GetAxisY())); + if (projection == VolumeProjection_Sagittal) + { + ASSERT_TRUE(IsEqualVectorL1(plane.GetOrigin(), s.GetOrigin() + static_cast(i) * + (-s.GetNormal()) * g.GetVoxelDimensions(projection)[2])); + } + else + { + ASSERT_TRUE(IsEqualVectorL1(plane.GetOrigin(), s.GetOrigin() + static_cast(i) * + s.GetNormal() * g.GetVoxelDimensions(projection)[2])); + } + + ASSERT_TRUE(IsEqualVectorL1(plane.GetAxisX(), s.GetAxisX())); + ASSERT_TRUE(IsEqualVectorL1(plane.GetAxisY(), s.GetAxisY())); unsigned int slice; - OrthancStone::VolumeProjection q; + VolumeProjection q; ASSERT_TRUE(g.DetectSlice(q, slice, plane)); ASSERT_EQ(projection, q); - ASSERT_EQ(i, slice); + ASSERT_EQ(i, slice); } } } + +TEST(LinearAlgebra, ParseVectorLocale) +{ + OrthancStone::Vector v; + + ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "1.2")); + ASSERT_EQ(1u, v.size()); + ASSERT_DOUBLE_EQ(1.2, v[0]); + + ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "-1.2e+2")); + ASSERT_EQ(1u, v.size()); + ASSERT_DOUBLE_EQ(-120.0, v[0]); + + ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "-1e-2\\2")); + ASSERT_EQ(2u, v.size()); + ASSERT_DOUBLE_EQ(-0.01, v[0]); + ASSERT_DOUBLE_EQ(2.0, v[1]); + + ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "1.3671875\\1.3671875")); + ASSERT_EQ(2u, v.size()); + ASSERT_DOUBLE_EQ(1.3671875, v[0]); + ASSERT_DOUBLE_EQ(1.3671875, v[1]); +} + + int main(int argc, char **argv) { Orthanc::Logging::Initialize();