Mercurial > hg > orthanc-stone
changeset 828:28f99af358fa
Merge + FusionMprSdl
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Wed, 29 May 2019 16:15:04 +0200 |
parents | 2fd96a637a59 (diff) 5c7b08bf84af (current diff) |
children | 3a984741686f |
files | Framework/Loaders/DicomStructureSetLoader.cpp Framework/Messages/MessageBroker.h Framework/Oracle/ThreadedOracle.h Samples/Sdl/FusionMprSdl.cpp Samples/Sdl/FusionMprSdl.h Samples/Sdl/Loader.cpp |
diffstat | 8 files changed, 1051 insertions(+), 17 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Loaders/DicomStructureSetLoader.cpp Wed May 29 13:42:34 2019 +0200 +++ b/Framework/Loaders/DicomStructureSetLoader.cpp Wed May 29 16:15:04 2019 +0200 @@ -128,7 +128,7 @@ std::set<std::string> instances; loader.content_->GetReferencedInstances(instances); - loader.countReferencedInstances_ = instances.size(); + loader.countReferencedInstances_ = static_cast<unsigned int>(instances.size()); for (std::set<std::string>::const_iterator it = instances.begin(); it != instances.end(); ++it)
--- a/Framework/Messages/MessageBroker.h Wed May 29 13:42:34 2019 +0200 +++ b/Framework/Messages/MessageBroker.h Wed May 29 16:15:04 2019 +0200 @@ -18,8 +18,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ +#pragma once -#pragma once +#include "../StoneException.h" #include "boost/noncopyable.hpp" @@ -40,6 +41,13 @@ std::set<const IObserver*> activeObservers_; // the list of observers that are currently alive (that have not been deleted) public: + MessageBroker() + { + static bool created = false; + ORTHANC_ASSERT(!created, "One broker to rule them all!"); + created = true; + } + void Register(const IObserver& observer) { activeObservers_.insert(&observer);
--- a/Framework/Oracle/ThreadedOracle.h Wed May 29 13:42:34 2019 +0200 +++ b/Framework/Oracle/ThreadedOracle.h Wed May 29 16:15:04 2019 +0200 @@ -74,6 +74,7 @@ virtual ~ThreadedOracle(); + // The reference is not stored. void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc); void SetThreadsCount(unsigned int count);
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp Wed May 29 13:42:34 2019 +0200 +++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp Wed May 29 16:15:04 2019 +0200 @@ -27,13 +27,13 @@ #include <boost/math/constants/constants.hpp> #include <boost/make_shared.hpp> -// <HACK> -// REMOVE THIS -#ifndef NDEBUG -extern void -TrackerSample_SetInfoDisplayMessage(std::string key, std::string value); -#endif -// </HACK> +//// <HACK> +//// REMOVE THIS +//#ifndef NDEBUG +//extern void +//TrackerSample_SetInfoDisplayMessage(std::string key, std::string value); +//#endif +//// </HACK> namespace OrthancStone { @@ -175,6 +175,7 @@ SetTextLayerOutlineProperties( GetScene(), layerHolder_, buf, ScenePoint2D(pointX, pointY)); +#if 0 // TODO:make it togglable bool enableInfoDisplay = false; if (enableInfoDisplay) @@ -224,6 +225,7 @@ TrackerSample_SetInfoDisplayMessage("angleDeg", boost::lexical_cast<std::string>(angleDeg)); } +#endif } } else
--- a/Samples/Sdl/CMakeLists.txt Wed May 29 13:42:34 2019 +0200 +++ b/Samples/Sdl/CMakeLists.txt Wed May 29 16:15:04 2019 +0200 @@ -55,19 +55,19 @@ ${ORTHANC_STONE_SOURCES} ) +# +# BasicScene +# + add_executable(BasicScene BasicScene.cpp ) target_link_libraries(BasicScene OrthancStone) -if(ENABLE_SDL_CONSOLE) - add_definitions( - -DENABLE_SDL_CONSOLE=1 - ) - LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.c") - LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.h") -endif() +# +# BasicScene +# LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp") LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.cpp") @@ -83,8 +83,23 @@ target_link_libraries(TrackerSample OrthancStone) +# +# Loader +# + add_executable(Loader Loader.cpp ) target_link_libraries(Loader OrthancStone) + +# +# FusionMprSdl +# + +add_executable(FusionMprSdl + FusionMprSdl.cpp + FusionMprSdl.h +) + +target_link_libraries(FusionMprSdl OrthancStone)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/FusionMprSdl.cpp Wed May 29 16:15:04 2019 +0200 @@ -0,0 +1,799 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "FusionMprSdl.h" + +#include "../../Applications/Sdl/SdlOpenGLWindow.h" + +#include "../../Framework/StoneInitialization.h" + +#include "../../Framework/Scene2D/CairoCompositor.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" + +#include "../../Framework/Scene2DViewport/CreateLineMeasureTracker.h" +#include "../../Framework/Scene2DViewport/CreateAngleMeasureTracker.h" +#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" +#include "../../Framework/Scene2DViewport/MeasureTool.h" +#include "../../Framework/Scene2DViewport/PredeclaredTypes.h" + +#include "../../Framework/Volumes/VolumeSceneLayerSource.h" + +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Images/PngWriter.h> +#include <Core/Logging.h> +#include <Core/OrthancException.h> + +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/make_shared.hpp> + +#include <stdio.h> +#include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../../Framework/Oracle/ThreadedOracle.h" +#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" +#include "../../Framework/Loaders/OrthancMultiframeVolumeLoader.h" +#include "../../Framework/Loaders/DicomStructureSetLoader.h" +#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" +#include "../../Framework/Scene2D/LookupTableStyleConfigurator.h" +#include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h" +#include "Core/SystemToolbox.h" + +namespace OrthancStone +{ + const char* FusionMprMeasureToolToString(size_t i) + { + static const char* descs[] = { + "FusionMprGuiTool_Rotate", + "FusionMprGuiTool_Pan", + "FusionMprGuiTool_Zoom", + "FusionMprGuiTool_LineMeasure", + "FusionMprGuiTool_CircleMeasure", + "FusionMprGuiTool_AngleMeasure", + "FusionMprGuiTool_EllipseMeasure", + "FusionMprGuiTool_LAST" + }; + if (i >= FusionMprGuiTool_LAST) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong tool index"); + } + return descs[i]; + } + + boost::shared_ptr<Scene2D> FusionMprSdlApp::GetScene() + { + return controller_->GetScene(); + } + + boost::shared_ptr<const Scene2D> FusionMprSdlApp::GetScene() const + { + return controller_->GetScene(); + } + + void FusionMprSdlApp::SelectNextTool() + { + currentTool_ = static_cast<FusionMprGuiTool>(currentTool_ + 1); + if (currentTool_ == FusionMprGuiTool_LAST) + currentTool_ = static_cast<FusionMprGuiTool>(0);; + printf("Current tool is now: %s\n", FusionMprMeasureToolToString(currentTool_)); + } + + void FusionMprSdlApp::DisplayInfoText() + { + // do not try to use stuff too early! + if (compositor_.get() == NULL) + return; + + std::stringstream msg; + + for (std::map<std::string, std::string>::const_iterator kv = infoTextMap_.begin(); + kv != infoTextMap_.end(); ++kv) + { + msg << kv->first << " : " << kv->second << std::endl; + } + std::string msgS = msg.str(); + + TextSceneLayer* layerP = NULL; + if (GetScene()->HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = dynamic_cast<TextSceneLayer&>( + GetScene()->GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); + layerP = &layer; + } + else + { + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer); + layerP = layer.get(); + layer->SetColor(0, 255, 0); + layer->SetFontIndex(1); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_TopLeft); + //layer->SetPosition(0,0); + GetScene()->SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release()); + } + // position the fixed info text in the upper right corner + layerP->SetText(msgS.c_str()); + double cX = compositor_->GetCanvasWidth() * (-0.5); + double cY = compositor_->GetCanvasHeight() * (-0.5); + GetScene()->GetCanvasToSceneTransform().Apply(cX,cY); + layerP->SetPosition(cX, cY); + } + + void FusionMprSdlApp::DisplayFloatingCtrlInfoText(const PointerEvent& e) + { + ScenePoint2D p = e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform()); + + char buf[128]; + sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)", + p.GetX(), p.GetY(), + e.GetMainPosition().GetX(), e.GetMainPosition().GetY()); + + if (GetScene()->HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = + dynamic_cast<TextSceneLayer&>(GetScene()->GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer); + layer->SetColor(0, 255, 0); + layer->SetText(buf); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_BottomCenter); + layer->SetPosition(p.GetX(), p.GetY()); + GetScene()->SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release()); + } + } + + void FusionMprSdlApp::HideInfoText() + { + GetScene()->DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX); + } + + void FusionMprSdlApp::HandleApplicationEvent( + const SDL_Event & event) + { + DisplayInfoText(); + + if (event.type == SDL_MOUSEMOTION) + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + if (activeTracker_.get() == NULL && + SDL_SCANCODE_LALT < scancodeCount && + keyboardState[SDL_SCANCODE_LALT]) + { + // The "left-ctrl" key is down, while no tracker is present + // Let's display the info text + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates( + event.button.x, event.button.y)); + + DisplayFloatingCtrlInfoText(e); + } + else + { + HideInfoText(); + //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)"; + if (activeTracker_.get() != NULL) + { + //LOG(TRACE) << "(activeTracker_.get() != NULL)"; + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates( + event.button.x, event.button.y)); + + //LOG(TRACE) << "event.button.x = " << event.button.x << " " << + // "event.button.y = " << event.button.y; + LOG(TRACE) << "activeTracker_->PointerMove(e); " << + e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY(); + + activeTracker_->PointerMove(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + } + } + else if (event.type == SDL_MOUSEBUTTONUP) + { + if (activeTracker_) + { + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates(event.button.x, event.button.y)); + activeTracker_->PointerUp(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + PointerEvent e; + e.AddPosition(compositor_->GetPixelCenterCoordinates( + event.button.x, event.button.y)); + if (activeTracker_) + { + activeTracker_->PointerDown(e); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + else + { + // we ATTEMPT to create a tracker if need be + activeTracker_ = CreateSuitableTracker(event, e); + } + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_ESCAPE: + if (activeTracker_) + { + activeTracker_->Cancel(); + if (!activeTracker_->IsAlive()) + activeTracker_.reset(); + } + break; + + case SDLK_t: + if (!activeTracker_) + SelectNextTool(); + else + { + LOG(WARNING) << "You cannot change the active tool when an interaction" + " is taking place"; + } + break; + case SDLK_s: + controller_->FitContent(compositor_->GetCanvasWidth(), + compositor_->GetCanvasHeight()); + break; + + case SDLK_z: + LOG(TRACE) << "SDLK_z has been pressed. event.key.keysym.mod == " << event.key.keysym.mod; + if (event.key.keysym.mod & KMOD_CTRL) + { + if (controller_->CanUndo()) + { + LOG(TRACE) << "Undoing..."; + controller_->Undo(); + } + else + { + LOG(WARNING) << "Nothing to undo!!!"; + } + } + break; + + case SDLK_y: + LOG(TRACE) << "SDLK_y has been pressed. event.key.keysym.mod == " << event.key.keysym.mod; + if (event.key.keysym.mod & KMOD_CTRL) + { + if (controller_->CanRedo()) + { + LOG(TRACE) << "Redoing..."; + controller_->Redo(); + } + else + { + LOG(WARNING) << "Nothing to redo!!!"; + } + } + break; + + case SDLK_c: + TakeScreenshot( + "screenshot.png", + compositor_->GetCanvasWidth(), + compositor_->GetCanvasHeight()); + break; + + default: + break; + } + } + } + + + void FusionMprSdlApp::OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message) + { + DisplayInfoText(); + } + + boost::shared_ptr<IFlexiblePointerTracker> FusionMprSdlApp::CreateSuitableTracker( + const SDL_Event & event, + const PointerEvent & e) + { + using namespace Orthanc; + + switch (event.button.button) + { + case SDL_BUTTON_MIDDLE: + return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker + (controller_, e)); + + case SDL_BUTTON_RIGHT: + return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker + (controller_, e, compositor_->GetCanvasHeight())); + + case SDL_BUTTON_LEFT: + { + //LOG(TRACE) << "CreateSuitableTracker: case SDL_BUTTON_LEFT:"; + // TODO: we need to iterate on the set of measuring tool and perform + // a hit test to check if a tracker needs to be created for edition. + // Otherwise, depending upon the active tool, we might want to create + // a "measuring tool creation" tracker + + // TODO: if there are conflicts, we should prefer a tracker that + // pertains to the type of measuring tool currently selected (TBD?) + boost::shared_ptr<IFlexiblePointerTracker> hitTestTracker = TrackerHitTest(e); + + if (hitTestTracker != NULL) + { + //LOG(TRACE) << "hitTestTracker != NULL"; + return hitTestTracker; + } + else + { + switch (currentTool_) + { + case FusionMprGuiTool_Rotate: + //LOG(TRACE) << "Creating RotateSceneTracker"; + return boost::shared_ptr<IFlexiblePointerTracker>(new RotateSceneTracker( + controller_, e)); + case FusionMprGuiTool_Pan: + return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker( + controller_, e)); + case FusionMprGuiTool_Zoom: + return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker( + controller_, e, compositor_->GetCanvasHeight())); + //case GuiTool_AngleMeasure: + // return new AngleMeasureTracker(GetScene(), e); + //case GuiTool_CircleMeasure: + // return new CircleMeasureTracker(GetScene(), e); + //case GuiTool_EllipseMeasure: + // return new EllipseMeasureTracker(GetScene(), e); + case FusionMprGuiTool_LineMeasure: + return boost::shared_ptr<IFlexiblePointerTracker>(new CreateLineMeasureTracker( + IObserver::GetBroker(), controller_, e)); + case FusionMprGuiTool_AngleMeasure: + return boost::shared_ptr<IFlexiblePointerTracker>(new CreateAngleMeasureTracker( + IObserver::GetBroker(), controller_, e)); + case FusionMprGuiTool_CircleMeasure: + LOG(ERROR) << "Not implemented yet!"; + return boost::shared_ptr<IFlexiblePointerTracker>(); + case FusionMprGuiTool_EllipseMeasure: + LOG(ERROR) << "Not implemented yet!"; + return boost::shared_ptr<IFlexiblePointerTracker>(); + default: + throw OrthancException(ErrorCode_InternalError, "Wrong tool!"); + } + } + } + default: + return boost::shared_ptr<IFlexiblePointerTracker>(); + } + } + + + FusionMprSdlApp::FusionMprSdlApp(MessageBroker& broker) + : IObserver(broker) + , broker_(broker) + , oracleObservable_(broker) + , oracle_(*this) + , currentTool_(FusionMprGuiTool_Rotate) + { + //oracleObservable.RegisterObserverCallback + //(new Callable + // <FusionMprSdlApp, SleepOracleCommand::TimeoutMessage>(*this, &FusionMprSdlApp::Handle)); + + //oracleObservable.RegisterObserverCallback + //(new Callable + // <Toto, GetOrthancImageCommand::SuccessMessage>(*this, &FusionMprSdlApp::Handle)); + + //oracleObservable.RegisterObserverCallback + //(new Callable + // <FusionMprSdlApp, GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &ToFusionMprSdlAppto::Handle)); + + oracleObservable_.RegisterObserverCallback + (new Callable + <FusionMprSdlApp, OracleCommandExceptionMessage>(*this, &FusionMprSdlApp::Handle)); + + controller_ = boost::shared_ptr<ViewportController>( + new ViewportController(broker_)); + + controller_->RegisterObserverCallback( + new Callable<FusionMprSdlApp, ViewportController::SceneTransformChanged> + (*this, &FusionMprSdlApp::OnSceneTransformChanged)); + + TEXTURE_2x2_1_ZINDEX = 1; + TEXTURE_1x1_ZINDEX = 2; + TEXTURE_2x2_2_ZINDEX = 3; + LINESET_1_ZINDEX = 4; + LINESET_2_ZINDEX = 5; + FLOATING_INFOTEXT_LAYER_ZINDEX = 6; + FIXED_INFOTEXT_LAYER_ZINDEX = 7; + } + + void FusionMprSdlApp::PrepareScene() + { + // Texture of 2x2 size + { + Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false); + + uint8_t* p = reinterpret_cast<uint8_t*>(i.GetRow(0)); + p[0] = 255; + p[1] = 0; + p[2] = 0; + + p[3] = 0; + p[4] = 255; + p[5] = 0; + + p = reinterpret_cast<uint8_t*>(i.GetRow(1)); + p[0] = 0; + p[1] = 0; + p[2] = 255; + + p[3] = 255; + p[4] = 0; + p[5] = 0; + + GetScene()->SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i)); + } + } + + void FusionMprSdlApp::DisableTracker() + { + if (activeTracker_) + { + activeTracker_->Cancel(); + activeTracker_.reset(); + } + } + + void FusionMprSdlApp::TakeScreenshot(const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight) + { + CairoCompositor compositor(*GetScene(), canvasWidth, canvasHeight); + compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE_0, Orthanc::Encoding_Latin1); + compositor.Refresh(); + + Orthanc::ImageAccessor canvas; + compositor.GetCanvas().GetReadOnlyAccessor(canvas); + + Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); + Orthanc::ImageProcessing::Convert(png, canvas); + + Orthanc::PngWriter writer; + writer.WriteToFile(target, png); + } + + + boost::shared_ptr<IFlexiblePointerTracker> FusionMprSdlApp::TrackerHitTest(const PointerEvent & e) + { + // std::vector<boost::shared_ptr<MeasureTool>> measureTools_; + return boost::shared_ptr<IFlexiblePointerTracker>(); + } + + static void GLAPIENTRY + OpenGLMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) + { + if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) + { + fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), + type, severity, message); + } + } + + static bool g_stopApplication = false; + + + void FusionMprSdlApp::Handle(const DicomVolumeImage::GeometryReadyMessage& message) + { + printf("Geometry ready\n"); + + //plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry(); + //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry(); + plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry(); + plane_.SetOrigin(message.GetOrigin().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f)); + + //Refresh(); + } + + + void FusionMprSdlApp::Handle(const OracleCommandExceptionMessage& message) + { + printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType()); + + switch (message.GetCommand().GetType()) + { + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + printf("URI: [%s]\n", dynamic_cast<const GetOrthancWebViewerJpegCommand&> + (message.GetCommand()).GetUri().c_str()); + break; + + default: + break; + } + } + + void FusionMprSdlApp::SetVolume1(int depth, + const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + source1_.reset(new OrthancStone::VolumeSceneLayerSource(*controller_->GetScene(), depth, volume)); + + if (style != NULL) + { + source1_->SetConfigurator(style); + } + } + + void FusionMprSdlApp::SetVolume2(int depth, + const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + source2_.reset(new OrthancStone::VolumeSceneLayerSource(*controller_->GetScene(), depth, volume)); + + if (style != NULL) + { + source2_->SetConfigurator(style); + } + } + + void FusionMprSdlApp::SetStructureSet(int depth, + const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume) + { + source3_.reset(new OrthancStone::VolumeSceneLayerSource(*controller_->GetScene(), depth, volume)); + } + + void FusionMprSdlApp::Run() + { + // False means we do NOT let Windows treat this as a legacy application + // that needs to be scaled + SdlOpenGLWindow window("Hello", 1024, 1024, false); + + controller_->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight()); + + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); + + compositor_.reset(new OpenGLCompositor(window, *GetScene())); + + compositor_->SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_0, Orthanc::Encoding_Latin1); + compositor_->SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE_1, Orthanc::Encoding_Latin1); + + + //////// from loader + { + Orthanc::WebServiceParameters p; + //p.SetUrl("http://localhost:8043/"); + p.SetCredentials("orthanc", "orthanc"); + oracle_.SetOrthancParameters(p); + } + + //////// from Run + + boost::shared_ptr<DicomVolumeImage> ct(new DicomVolumeImage); + boost::shared_ptr<DicomVolumeImage> dose(new DicomVolumeImage); + + + boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader; + boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader; + boost::shared_ptr<DicomStructureSetLoader> rtstructLoader; + + { + ctLoader.reset(new OrthancSeriesVolumeProgressiveLoader(ct, oracle_, oracleObservable_)); + doseLoader.reset(new OrthancMultiframeVolumeLoader(dose, oracle_, oracleObservable_)); + rtstructLoader.reset(new DicomStructureSetLoader(oracle_, oracleObservable_)); + } + + //toto->SetReferenceLoader(*ctLoader); + //doseLoader->RegisterObserverCallback + //(new Callable + // <FusionMprSdlApp, DicomVolumeImage::GeometryReadyMessage>(*this, &FusionMprSdlApp::Handle)); + ctLoader->RegisterObserverCallback + (new Callable + <FusionMprSdlApp, DicomVolumeImage::GeometryReadyMessage>(*this, &FusionMprSdlApp::Handle)); + + this->SetVolume1(0, ctLoader, new GrayscaleStyleConfigurator); + + { + std::auto_ptr<LookupTableStyleConfigurator> config(new LookupTableStyleConfigurator); + config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); + + boost::shared_ptr<DicomVolumeImageMPRSlicer> tmp(new DicomVolumeImageMPRSlicer(dose)); + this->SetVolume2(1, tmp, config.release()); + } + + this->SetStructureSet(2, rtstructLoader); + +#if 1 + // BGO data + ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT + //doseLoader->LoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // RT-DOSE + //rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT +#else + //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT + //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE + //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6"); // RT-STRUCT + + // 2017-05-16 + ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT + doseLoader->LoadInstance("eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad"); // RT-DOSE + rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT +#endif + + oracle_.Start(); + +//// END from loader + + while (!g_stopApplication) + { + compositor_->Refresh(); + +//////// from loader + if (source1_.get() != NULL) + { + source1_->Update(plane_); + } + + if (source2_.get() != NULL) + { + source2_->Update(plane_); + } + + if (source3_.get() != NULL) + { + source3_->Update(plane_); + } +//// END from loader + + SDL_Event event; + while (!g_stopApplication && SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + g_stopApplication = true; + break; + } + else if (event.type == SDL_WINDOWEVENT && + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + DisableTracker(); // was: tracker.reset(NULL); + compositor_->UpdateSize(); + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + window.GetWindow().ToggleMaximize(); + break; + + case SDLK_q: + g_stopApplication = true; + break; + default: + break; + } + } + HandleApplicationEvent(event); + } + SDL_Delay(1); + } + + // the following is paramount because the compositor holds a reference + // to the scene and we do not want this reference to become dangling + compositor_.reset(NULL); + + //// from loader + + Orthanc::SystemToolbox::ServerBarrier(); + + /** + * WARNING => The oracle must be stopped BEFORE the objects using + * it are destroyed!!! This forces to wait for the completion of + * the running callback methods. Otherwise, the callbacks methods + * might still be running while their parent object is destroyed, + * resulting in crashes. This is very visible if adding a sleep(), + * as in (*). + **/ + + oracle_.Stop(); + //// END from loader + } + + void FusionMprSdlApp::SetInfoDisplayMessage( + std::string key, std::string value) + { + if (value == "") + infoTextMap_.erase(key); + else + infoTextMap_[key] = value; + DisplayInfoText(); + } + +} + + +boost::weak_ptr<OrthancStone::FusionMprSdlApp> g_app; + +void FusionMprSdl_SetInfoDisplayMessage(std::string key, std::string value) +{ + boost::shared_ptr<OrthancStone::FusionMprSdlApp> app = g_app.lock(); + if (app) + { + app->SetInfoDisplayMessage(key, value); + } +} + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + using namespace OrthancStone; + + StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); +// Orthanc::Logging::EnableTraceLevel(true); + + try + { + OrthancStone::MessageBroker broker; + boost::shared_ptr<FusionMprSdlApp> app(new FusionMprSdlApp(broker)); + g_app = app; + app->PrepareScene(); + app->Run(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/FusionMprSdl.h Wed May 29 16:15:04 2019 +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-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "../../Framework/Messages/IObserver.h" +#include "../../Framework/Messages/IMessageEmitter.h" +#include "../../Framework/Oracle/OracleCommandExceptionMessage.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/Volumes/DicomVolumeImage.h" +#include "../../Framework/Oracle/ThreadedOracle.h" + +#include <boost/enable_shared_from_this.hpp> +#include <boost/thread.hpp> +#include <boost/noncopyable.hpp> + +#include <SDL.h> + +namespace OrthancStone +{ + class OpenGLCompositor; + class IVolumeSlicer; + class ILayerStyleConfigurator; + class DicomStructureSetLoader; + class IOracle; + class ThreadedOracle; + class VolumeSceneLayerSource; + class NativeFusionMprApplicationContext; + + + enum FusionMprGuiTool + { + FusionMprGuiTool_Rotate = 0, + FusionMprGuiTool_Pan, + FusionMprGuiTool_Zoom, + FusionMprGuiTool_LineMeasure, + FusionMprGuiTool_CircleMeasure, + FusionMprGuiTool_AngleMeasure, + FusionMprGuiTool_EllipseMeasure, + FusionMprGuiTool_LAST + }; + + const char* MeasureToolToString(size_t i); + + static const unsigned int FONT_SIZE_0 = 32; + static const unsigned int FONT_SIZE_1 = 24; + + class Scene2D; + + /** + This application subclasses IMessageEmitter to use a mutex before forwarding Oracle messages (that + can be sent from multiple threads) + */ + class FusionMprSdlApp : public IObserver + , public boost::enable_shared_from_this<FusionMprSdlApp> + , public IMessageEmitter + { + public: + // 12 because. + FusionMprSdlApp(MessageBroker& broker); + + void PrepareScene(); + void Run(); + void SetInfoDisplayMessage(std::string key, std::string value); + void DisableTracker(); + + boost::shared_ptr<Scene2D> GetScene(); + boost::shared_ptr<const Scene2D> GetScene() const; + + void HandleApplicationEvent(const SDL_Event& event); + + /** + This method is called when the scene transform changes. It allows to + recompute the visual elements whose content depend upon the scene transform + */ + void OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message); + + + virtual void EmitMessage(const IObserver& observer, + const IMessage& message) ORTHANC_OVERRIDE + { + try + { + boost::unique_lock<boost::shared_mutex> lock(mutex_); + oracleObservable_.EmitMessage(observer, message); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while emitting a message: " << e.What(); + throw; + } + } + + private: +#if 1 + // if threaded (not wasm) + MessageBroker& broker_; + IObservable oracleObservable_; + ThreadedOracle oracle_; + boost::shared_mutex mutex_; // to serialize messages from the ThreadedOracle +#endif + + void SelectNextTool(); + + /** + This returns a random point in the canvas part of the scene, but in + scene coordinates + */ + ScenePoint2D GetRandomPointInScene() const; + + boost::shared_ptr<IFlexiblePointerTracker> TrackerHitTest(const PointerEvent& e); + + boost::shared_ptr<IFlexiblePointerTracker> CreateSuitableTracker( + const SDL_Event& event, + const PointerEvent& e); + + void TakeScreenshot( + const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight); + + /** + This adds the command at the top of the undo stack + */ + void Commit(boost::shared_ptr<TrackerCommand> cmd); + void Undo(); + void Redo(); + + + // TODO private + void Handle(const DicomVolumeImage::GeometryReadyMessage& message); + void Handle(const OracleCommandExceptionMessage& message); + + void SetVolume1( + int depth, + const boost::shared_ptr<IVolumeSlicer>& volume, + ILayerStyleConfigurator* style); + + void SetVolume2( + int depth, + const boost::shared_ptr<IVolumeSlicer>& volume, + ILayerStyleConfigurator* style); + + void SetStructureSet( + int depth, + const boost::shared_ptr<DicomStructureSetLoader>& volume); + + + + private: + void DisplayFloatingCtrlInfoText(const PointerEvent& e); + void DisplayInfoText(); + void HideInfoText(); + + private: + CoordinateSystem3D plane_; + + boost::shared_ptr<VolumeSceneLayerSource> source1_, source2_, source3_; + + std::auto_ptr<OpenGLCompositor> compositor_; + /** + WARNING: the measuring tools do store a reference to the scene, and it + paramount that the scene gets destroyed AFTER the measurement tools. + */ + boost::shared_ptr<ViewportController> controller_; + + std::map<std::string, std::string> infoTextMap_; + boost::shared_ptr<IFlexiblePointerTracker> activeTracker_; + + //static const int LAYER_POSITION = 150; + + int TEXTURE_2x2_1_ZINDEX; + int TEXTURE_1x1_ZINDEX; + int TEXTURE_2x2_2_ZINDEX; + int LINESET_1_ZINDEX; + int LINESET_2_ZINDEX; + int FLOATING_INFOTEXT_LAYER_ZINDEX; + int FIXED_INFOTEXT_LAYER_ZINDEX; + + FusionMprGuiTool currentTool_; + }; + +} + + + \ No newline at end of file
--- a/Samples/Sdl/Loader.cpp Wed May 29 13:42:34 2019 +0200 +++ b/Samples/Sdl/Loader.cpp Wed May 29 16:15:04 2019 +0200 @@ -57,7 +57,7 @@ virtual void EmitMessage(const IObserver& observer, - const IMessage& message) + const IMessage& message) ORTHANC_OVERRIDE { try { @@ -302,6 +302,8 @@ void Run(OrthancStone::NativeApplicationContext& context, OrthancStone::ThreadedOracle& oracle) { + // the oracle has been supplied with the context (as an IEmitter) upon + // creation boost::shared_ptr<OrthancStone::DicomVolumeImage> ct(new OrthancStone::DicomVolumeImage); boost::shared_ptr<OrthancStone::DicomVolumeImage> dose(new OrthancStone::DicomVolumeImage); @@ -314,6 +316,11 @@ { 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()));