Mercurial > hg > orthanc-stone
changeset 1200:54cbffabdc45 broker
integration mainline->broker
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 29 Nov 2019 11:03:41 +0100 |
parents | 4cc997207d8a (diff) 922d2e61aa5d (current diff) |
children | f3bb9a6dd949 |
files | Applications/Samples/SingleFrameEditorApplication.h Applications/Sdl/SdlEngine.cpp Framework/Radiography/RadiographyAlphaLayer.h Framework/Radiography/RadiographyDicomLayer.cpp Framework/Radiography/RadiographyDicomLayer.h Framework/Radiography/RadiographyLayer.h Framework/Radiography/RadiographyMaskLayer.h Framework/Radiography/RadiographyScene.cpp Framework/Radiography/RadiographyScene.h Framework/Radiography/RadiographyTextLayer.h Framework/Radiography/RadiographyWidget.cpp Framework/Radiography/RadiographyWidget.h Framework/StoneEnumerations.h |
diffstat | 193 files changed, 4030 insertions(+), 2648 deletions(-) [+] |
line wrap: on
line diff
--- a/Applications/Generic/GuiAdapter.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Generic/GuiAdapter.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -792,7 +792,7 @@ while (!stop) { { - LockingEmitter::WriterLock lock(lockingEmitter_); + Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); if(func != NULL) (*func)(cookie); OnAnimationFrame(); // in SDL we must call it @@ -802,7 +802,7 @@ while (!stop && SDL_PollEvent(&event)) { - LockingEmitter::WriterLock lock(lockingEmitter_); + Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); if (event.type == SDL_QUIT) {
--- a/Applications/Generic/GuiAdapter.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Generic/GuiAdapter.h Fri Nov 29 11:03:41 2019 +0100 @@ -95,7 +95,10 @@ struct GuiAdapterWheelEvent; struct GuiAdapterKeyboardEvent; - class LockingEmitter; + namespace Deprecated + { + class LockingEmitter; + } #if 1 typedef bool (*OnMouseEventFunc)(std::string canvasId, const GuiAdapterMouseEvent* mouseEvent, void* userData); @@ -225,7 +228,7 @@ { public: #if ORTHANC_ENABLE_THREADS == 1 - GuiAdapter(LockingEmitter& lockingEmitter) : lockingEmitter_(lockingEmitter) + GuiAdapter(Deprecated::LockingEmitter& lockingEmitter) : lockingEmitter_(lockingEmitter) #else GuiAdapter() #endif @@ -301,7 +304,7 @@ This object is used by the multithreaded Oracle to serialize access to shared data. We need to use it as soon as we access the state. */ - LockingEmitter& lockingEmitter_; + Deprecated::LockingEmitter& lockingEmitter_; #endif /**
--- a/Applications/Generic/NativeStoneApplicationContext.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Generic/NativeStoneApplicationContext.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -24,10 +24,10 @@ namespace OrthancStone { - Deprecated::IWidget& NativeStoneApplicationContext::GlobalMutexLocker::SetCentralWidget(Deprecated::IWidget* widget) + void NativeStoneApplicationContext::GlobalMutexLocker::SetCentralWidget( + boost::shared_ptr<Deprecated::IWidget> 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 {
--- a/Applications/Generic/NativeStoneApplicationContext.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Generic/NativeStoneApplicationContext.h Fri Nov 29 11:03:41 2019 +0100 @@ -56,7 +56,7 @@ { } - Deprecated::IWidget& SetCentralWidget(Deprecated::IWidget* widget); // Takes ownership + void SetCentralWidget(boost::shared_ptr<Deprecated::IWidget> widget); Deprecated::IViewport& GetCentralViewport() { @@ -67,14 +67,9 @@ { that_.updateDelayInMs_ = delayInMs; } - - MessageBroker& GetMessageBroker() - { - return that_.GetMessageBroker(); - } }; - NativeStoneApplicationContext(MessageBroker& broker); + NativeStoneApplicationContext(); void Start();
--- a/Applications/Generic/NativeStoneApplicationRunner.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Generic/NativeStoneApplicationRunner.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<Deprecated::OracleWebService> 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) {
--- a/Applications/Generic/NativeStoneApplicationRunner.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Generic/NativeStoneApplicationRunner.h Fri Nov 29 11:03:41 2019 +0100 @@ -34,14 +34,11 @@ class NativeStoneApplicationRunner { protected: - MessageBroker& broker_; - IStoneApplication& application_; + boost::shared_ptr<IStoneApplication> application_; + public: - - NativeStoneApplicationRunner(MessageBroker& broker, - IStoneApplication& application) - : broker_(broker), - application_(application) + NativeStoneApplicationRunner(boost::shared_ptr<IStoneApplication> application) + : application_(application) { } int Execute(int argc,
--- a/Applications/IStoneApplication.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/IStoneApplication.h Fri Nov 29 11:03:41 2019 +0100 @@ -64,7 +64,11 @@ #endif virtual std::string GetTitle() const = 0; - virtual Deprecated::IWidget* GetCentralWidget() = 0; + + virtual void SetCentralWidget(boost::shared_ptr<Deprecated::IWidget> widget) = 0; + + virtual boost::shared_ptr<Deprecated::IWidget> GetCentralWidget() = 0; + virtual void Finalize() = 0; }; }
--- a/Applications/Samples/SampleApplicationBase.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Samples/SampleApplicationBase.h Fri Nov 29 11:03:41 2019 +0100 @@ -40,9 +40,8 @@ { class SampleApplicationBase : public IStoneApplication { - protected: - // ownership is transferred to the application context - Deprecated::WorldSceneWidget* mainWidget_; + private: + boost::shared_ptr<Deprecated::IWidget> mainWidget_; public: virtual void Initialize(StoneApplicationContext* context, @@ -64,7 +63,16 @@ virtual void Finalize() ORTHANC_OVERRIDE {} - virtual Deprecated::IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainWidget_;} + + virtual void SetCentralWidget(boost::shared_ptr<Deprecated::IWidget> widget) ORTHANC_OVERRIDE + { + mainWidget_ = widget; + } + + virtual boost::shared_ptr<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
--- a/Applications/Samples/SampleMainNative.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Samples/SampleMainNative.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -26,19 +26,18 @@ #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); + boost::shared_ptr<SampleApplication> sampleStoneApplication(new SampleApplication); #if ORTHANC_ENABLE_SDL==1 - OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(broker, sampleStoneApplication); + OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(sampleStoneApplication); return sdlApplicationRunner.Execute(argc, argv); #endif + #if ORTHANC_ENABLE_QT==1 - OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication); + OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(sampleStoneApplication); return qtAppRunner.Execute(argc, argv); #endif }
--- a/Applications/Samples/SimpleViewerApplicationSingleFile.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h Fri Nov 29 11:03:41 2019 +0100 @@ -44,7 +44,7 @@ { class SimpleViewerApplication : public SampleSingleCanvasWithButtonsApplicationBase, - public IObserver + public ObserverBase<SimpleViewerApplication> { private: class ThumbnailInteractor : public Deprecated::IWorldSceneInteractor @@ -199,8 +199,8 @@ SimpleViewerApplication& viewerApplication_; public: - SimpleViewerApplicationAdapter(MessageBroker& broker, SimpleViewerApplication& application) - : WasmPlatformApplicationAdapter(broker, application), + SimpleViewerApplicationAdapter(SimpleViewerApplication& application) + : WasmPlatformApplicationAdapter(application), viewerApplication_(application) { } @@ -243,7 +243,7 @@ std::auto_ptr<ThumbnailInteractor> thumbnailInteractor_; Deprecated::LayoutWidget* mainLayout_; Deprecated::LayoutWidget* thumbnailsLayout_; - std::vector<Deprecated::SliceViewerWidget*> thumbnails_; + std::vector<boost::shared_ptr<Deprecated::SliceViewerWidget> > thumbnails_; std::map<std::string, std::vector<std::string> > instancesIdsPerSeriesId_; std::map<std::string, Json::Value> seriesTags_; @@ -258,8 +258,7 @@ Orthanc::Font font_; public: - SimpleViewerApplication(MessageBroker& broker) : - IObserver(broker), + SimpleViewerApplication() : currentTool_(Tool_LineMeasure), mainLayout_(NULL), currentInstanceIndex_(0), @@ -297,26 +296,28 @@ mainLayout_->SetBackgroundColor(0, 0, 0); mainLayout_->SetHorizontal(); - thumbnailsLayout_ = new Deprecated::LayoutWidget("thumbnail-layout"); + boost::shared_ptr<Deprecated::LayoutWidget> 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"); + boost::shared_ptr<Deprecated::SliceViewerWidget> widget + (new Deprecated::SliceViewerWidget("main-viewport")); + SetCentralWidget(widget); //mainWidget_->RegisterObserver(*this); // hierarchy mainLayout_->AddWidget(thumbnailsLayout_); - mainLayout_->AddWidget(mainWidget_); + mainLayout_->AddWidget(widget); // sources - smartLoader_.reset(new Deprecated::SmartLoader(GetBroker(), context->GetOrthancApiClient())); + smartLoader_.reset(new Deprecated::SmartLoader(context->GetOrthancApiClient())); smartLoader_->SetImageQuality(Deprecated::SliceImageQuality_FullPam); mainLayout_->SetTransmitMouseOver(true); mainWidgetInteractor_.reset(new MainWidgetInteractor(*this)); - mainWidget_->SetInteractor(*mainWidgetInteractor_); + widget->SetInteractor(*mainWidgetInteractor_); thumbnailInteractor_.reset(new ThumbnailInteractor(*this)); } @@ -327,10 +328,10 @@ if (parameters.count("studyId") < 1) { LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc"; - context->GetOrthancApiClient().GetJsonAsync( + context->GetOrthancApiClient()->GetJsonAsync( "/studies", new Callable<SimpleViewerApplication, Deprecated::OrthancApiClient::JsonResponseReadyMessage> - (*this, &SimpleViewerApplication::OnStudyListReceived)); + (GetSharedObserver(), &SimpleViewerApplication::OnStudyListReceived)); } else { @@ -357,10 +358,10 @@ { for (size_t i=0; i < response["Series"].size(); i++) { - context_->GetOrthancApiClient().GetJsonAsync( + context_->GetOrthancApiClient()->GetJsonAsync( "/series/" + response["Series"][(int)i].asString(), new Callable<SimpleViewerApplication, Deprecated::OrthancApiClient::JsonResponseReadyMessage> - (*this, &SimpleViewerApplication::OnSeriesReceived)); + (GetSharedObserver(), &SimpleViewerApplication::OnSeriesReceived)); } } } @@ -387,7 +388,7 @@ LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]); // if this is the first thumbnail loaded, load the first instance in the mainWidget - Deprecated::SliceViewerWidget& widget = *dynamic_cast<Deprecated::SliceViewerWidget*>(mainWidget_); + Deprecated::SliceViewerWidget& widget = dynamic_cast<Deprecated::SliceViewerWidget&>(*GetCentralWidget()); if (widget.GetLayerCount() == 0) { smartLoader_->SetFrameInWidget(widget, 0, instancesIdsPerSeriesId_[seriesId][0], 0); @@ -398,10 +399,10 @@ 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); + boost::shared_ptr<Deprecated::SliceViewerWidget> thumbnailWidget(new Deprecated::SliceViewerWidget("thumbnail-series-" + seriesId)); thumbnails_.push_back(thumbnailWidget); thumbnailsLayout_->AddWidget(thumbnailWidget); - thumbnailWidget->RegisterObserverCallback(new Callable<SimpleViewerApplication, Deprecated::SliceViewerWidget::GeometryChangedMessage>(*this, &SimpleViewerApplication::OnWidgetGeometryChanged)); + Register<Deprecated::SliceViewerWidget::GeometryChangedMessage>(*thumbnailWidget, &SimpleViewerApplication::OnWidgetGeometryChanged); smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0); thumbnailWidget->SetInteractor(*thumbnailInteractor_); } @@ -409,9 +410,9 @@ void SelectStudy(const std::string& studyId) { LOG(INFO) << "Selecting study: " << studyId; - context_->GetOrthancApiClient().GetJsonAsync( + context_->GetOrthancApiClient()->GetJsonAsync( "/studies/" + studyId, new Callable<SimpleViewerApplication, Deprecated::OrthancApiClient::JsonResponseReadyMessage> - (*this, &SimpleViewerApplication::OnStudyReceived)); + (GetSharedObserver(), &SimpleViewerApplication::OnStudyReceived)); } void OnWidgetGeometryChanged(const Deprecated::SliceViewerWidget::GeometryChangedMessage& message) @@ -422,7 +423,7 @@ void SelectSeriesInMainViewport(const std::string& seriesId) { - Deprecated::SliceViewerWidget& widget = *dynamic_cast<Deprecated::SliceViewerWidget*>(mainWidget_); + Deprecated::SliceViewerWidget& widget = dynamic_cast<Deprecated::SliceViewerWidget&>(*GetCentralWidget()); smartLoader_->SetFrameInWidget(widget, 0, instancesIdsPerSeriesId_[seriesId][0], 0); } @@ -451,7 +452,7 @@ virtual void InitializeWasm() { AttachWidgetToWasmViewport("canvas", thumbnailsLayout_); - AttachWidgetToWasmViewport("canvas2", mainWidget_); + AttachWidgetToWasmViewport("canvas2", widget); } #endif
--- a/Applications/Samples/SingleFrameApplication.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Samples/SingleFrameApplication.h Fri Nov 29 11:03:41 2019 +0100 @@ -38,7 +38,7 @@ { class SingleFrameApplication : public SampleSingleCanvasApplicationBase, - public IObserver + public ObserverBase<SingleFrameApplication> { private: class Interactor : public Deprecated::IWorldSceneInteractor @@ -127,7 +127,7 @@ void OffsetSlice(int offset) { - if (source_ != NULL) + if (source_) { int slice = static_cast<int>(slice_) + offset; @@ -149,21 +149,15 @@ } - Deprecated::SliceViewerWidget& GetMainWidget() - { - return *dynamic_cast<Deprecated::SliceViewerWidget*>(mainWidget_); - } - - void SetSlice(size_t index) { - if (source_ != NULL && + if (source_ && index < source_->GetSlicesCount()) { slice_ = static_cast<unsigned int>(index); #if 1 - GetMainWidget().SetSlice(source_->GetSlice(slice_).GetGeometry()); + widget_->SetSlice(source_->GetSlice(slice_).GetGeometry()); #else // TEST for scene extents - Rotate the axes double a = 15.0 / 180.0 * boost::math::constants::pi<double>(); @@ -189,22 +183,22 @@ // 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()) + if (source_ && + source_.get() == &message.GetOrigin()) { SetSlice(source_->GetSlicesCount() / 2); } - GetMainWidget().FitContent(); + widget_->FitContent(); } - + + boost::shared_ptr<Deprecated::SliceViewerWidget> widget_; std::auto_ptr<Interactor> mainWidgetInteractor_; - const Deprecated::DicomSeriesVolumeSlicer* source_; + boost::shared_ptr<Deprecated::DicomSeriesVolumeSlicer> source_; unsigned int slice_; public: - SingleFrameApplication(MessageBroker& broker) : - IObserver(broker), - source_(NULL), + SingleFrameApplication() : slice_(0) { } @@ -243,13 +237,15 @@ std::string instance = parameters["instance"].as<std::string>(); int frame = parameters["frame"].as<unsigned int>(); - mainWidget_ = new Deprecated::SliceViewerWidget(GetBroker(), "main-widget"); + widget_.reset(new Deprecated::SliceViewerWidget("main-widget")); + SetCentralWidget(widget_); - std::auto_ptr<Deprecated::DicomSeriesVolumeSlicer> layer(new Deprecated::DicomSeriesVolumeSlicer(GetBroker(), context->GetOrthancApiClient())); - source_ = layer.get(); + boost::shared_ptr<Deprecated::DicomSeriesVolumeSlicer> layer(new Deprecated::DicomSeriesVolumeSlicer); + layer->Connect(context->GetOrthancApiClient()); + source_ = layer; layer->LoadFrame(instance, frame); - layer->RegisterObserverCallback(new Callable<SingleFrameApplication, Deprecated::IVolumeSlicer::GeometryReadyMessage>(*this, &SingleFrameApplication::OnMainWidgetGeometryReady)); - GetMainWidget().AddLayer(layer.release()); + Register<Deprecated::IVolumeSlicer::GeometryReadyMessage>(*layer, &SingleFrameApplication::OnMainWidgetGeometryReady); + widget_->AddLayer(layer); Deprecated::RenderStyle s; @@ -258,11 +254,11 @@ s.interpolation_ = ImageInterpolation_Bilinear; } - GetMainWidget().SetLayerStyle(0, s); - GetMainWidget().SetTransmitMouseOver(true); + widget_->SetLayerStyle(0, s); + widget_->SetTransmitMouseOver(true); mainWidgetInteractor_.reset(new Interactor(*this)); - GetMainWidget().SetInteractor(*mainWidgetInteractor_); + widget_->SetInteractor(*mainWidgetInteractor_); } };
--- a/Applications/Samples/SingleFrameEditorApplication.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Samples/SingleFrameEditorApplication.h Fri Nov 29 11:03:41 2019 +0100 @@ -28,13 +28,13 @@ #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/Radiography/RadiographySceneWriter.h" -#include "../../Framework/Radiography/RadiographySceneReader.h" -#include "../../Framework/Radiography/RadiographyMaskLayer.h" #include "../../Framework/Toolbox/TextRenderer.h" #include <Core/HttpClient.h> @@ -55,7 +55,7 @@ { class RadiographyEditorInteractor : public Deprecated::IWorldSceneInteractor, - public IObserver + public ObserverBase<RadiographyEditorInteractor> { private: enum Tool @@ -82,8 +82,7 @@ public: - RadiographyEditorInteractor(MessageBroker& broker) : - IObserver(broker), + RadiographyEditorInteractor() : context_(NULL), tool_(Tool_Move), maskLayer_(NULL) @@ -315,7 +314,7 @@ LOG(INFO) << "JSON export was successful: " << snapshot.toStyledString(); - boost::shared_ptr<RadiographyScene> scene(new RadiographyScene(GetBroker())); + boost::shared_ptr<RadiographyScene> scene(new RadiographyScene); RadiographySceneReader reader(*scene, context_->GetOrthancApiClient()); reader.Read(snapshot); @@ -346,7 +345,7 @@ if (context_ != NULL) { - widget.GetScene().ExportDicom(context_->GetOrthancApiClient(), + widget.GetScene().ExportDicom(*context_->GetOrthancApiClient(), tags, std::string(), 0.1, 0.1, widget.IsInverted(), widget.GetInterpolation(), EXPORT_USING_PAM); } @@ -426,16 +425,9 @@ private: boost::shared_ptr<RadiographyScene> scene_; RadiographyEditorInteractor interactor_; - Orthanc::FontRegistry fontRegistry_; RadiographyMaskLayer* maskLayer_; public: - SingleFrameEditorApplication(MessageBroker& broker) : - IObserver(broker), - interactor_(broker) - { - } - virtual ~SingleFrameEditorApplication() { LOG(WARNING) << "Destroying the application"; @@ -487,9 +479,9 @@ std::string instance = parameters["instance"].as<std::string>(); //int frame = parameters["frame"].as<unsigned int>(); - scene_.reset(new RadiographyScene(GetBroker())); + scene_.reset(new RadiographyScene); - RadiographyLayer& dicomLayer = scene_->LoadDicomFrame(context->GetOrthancApiClient(), instance, 0, false, NULL); + RadiographyLayer& dicomLayer = scene_->LoadDicomFrame(*context->GetOrthancApiClient(), instance, 0, false, NULL); //scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0); // = scene_->LoadDicomFrame(context->GetOrthancApiClient(), "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false, NULL); @@ -527,10 +519,10 @@ layer.SetPan(0, 200); } - - mainWidget_ = new RadiographyWidget(GetBroker(), scene_, "main-widget"); - mainWidget_->SetTransmitMouseOver(true); - mainWidget_->SetInteractor(interactor_); + boost::shared_ptr<RadiographyWidget> widget(new RadiographyWidget(scene_, "main-widget")); + widget->SetTransmitMouseOver(true); + widget->SetInteractor(interactor_); + SetCentralWidget(widget); //scene_->SetWindowing(128, 256); }
--- a/Applications/Sdl/SdlCairoSurface.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Sdl/SdlCairoSurface.h Fri Nov 29 11:03:41 2019 +0100 @@ -27,6 +27,8 @@ #include "../../Framework/Wrappers/CairoSurface.h" #include "../../Framework/Deprecated/Viewport/IViewport.h" +#include <SDL_render.h> + #include <boost/thread/mutex.hpp> namespace OrthancStone
--- a/Applications/Sdl/SdlEngine.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Sdl/SdlEngine.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -99,9 +99,7 @@ SdlEngine::SdlEngine(SdlWindow& window, - NativeStoneApplicationContext& context, - MessageBroker& broker) : - IObserver(broker), + NativeStoneApplicationContext& context) : window_(window), context_(context), surface_(window),
--- a/Applications/Sdl/SdlEngine.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Sdl/SdlEngine.h Fri Nov 29 11:03:41 2019 +0100 @@ -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<SdlEngine> { private: SdlWindow& window_; @@ -46,8 +47,7 @@ public: SdlEngine(SdlWindow& window, - NativeStoneApplicationContext& context, - MessageBroker& broker); + NativeStoneApplicationContext& context); void OnViewportChanged(const Deprecated::IViewport::ViewportChangedMessage& message) {
--- a/Applications/Sdl/SdlOrthancSurface.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Sdl/SdlOrthancSurface.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -27,6 +27,8 @@ #include <Core/OrthancException.h> #include <Core/Images/Image.h> +#include <SDL_render.h> + namespace OrthancStone { SdlOrthancSurface::SdlOrthancSurface(SdlWindow& window) :
--- a/Applications/Sdl/SdlStoneApplicationRunner.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Sdl/SdlStoneApplicationRunner.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<SdlEngine> sdl(new SdlEngine(window, context)); { NativeStoneApplicationContext::GlobalMutexLocker locker(context); - locker.GetCentralViewport().RegisterObserverCallback( - new Callable<SdlEngine, Deprecated::IViewport::ViewportChangedMessage> - (sdl, &SdlEngine::OnViewportChanged)); + sdl->Register<Deprecated::IViewport::ViewportChangedMessage> + (locker.GetCentralViewport(), &SdlEngine::OnViewportChanged); //context.GetCentralViewport().Register(sdl); // (*) } context.Start(); - sdl.Run(); + sdl->Run(); LOG(WARNING) << "Stopping the application";
--- a/Applications/Sdl/SdlStoneApplicationRunner.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/Sdl/SdlStoneApplicationRunner.h Fri Nov 29 11:03:41 2019 +0100 @@ -39,9 +39,8 @@ bool enableOpenGl_; public: - SdlStoneApplicationRunner(MessageBroker& broker, - IStoneApplication& application) : - NativeStoneApplicationRunner(broker, application) + SdlStoneApplicationRunner(boost::shared_ptr<IStoneApplication> application) : + NativeStoneApplicationRunner(application) { }
--- a/Applications/StoneApplicationContext.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/StoneApplicationContext.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<Deprecated::IWebService> StoneApplicationContext::GetWebService() { if (webService_ == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - return *webService_; + return webService_; } - Deprecated::OrthancApiClient& StoneApplicationContext::GetOrthancApiClient() + boost::shared_ptr<Deprecated::OrthancApiClient> 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<Deprecated::IWebService> webService) { - webService_ = &webService; + webService_ = webService; InitializeOrthanc(); }
--- a/Applications/StoneApplicationContext.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Applications/StoneApplicationContext.h Fri Nov 29 11:03:41 2019 +0100 @@ -59,18 +59,15 @@ class StoneApplicationContext : public boost::noncopyable { private: - MessageBroker& broker_; - Deprecated::IWebService* webService_; - Deprecated::IDelayedCallExecutor* delayedCallExecutor_; - std::auto_ptr<Deprecated::OrthancApiClient> orthanc_; + boost::shared_ptr<Deprecated::IWebService> webService_; + Deprecated::IDelayedCallExecutor* delayedCallExecutor_; // TODO => shared_ptr ?? + boost::shared_ptr<Deprecated::OrthancApiClient> 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<Deprecated::IWebService> GetWebService(); - bool HasWebService() const - { - return webService_ != NULL; - } + boost::shared_ptr<Deprecated::OrthancApiClient> GetOrthancApiClient(); - Deprecated::IWebService& GetWebService(); - - Deprecated::OrthancApiClient& GetOrthancApiClient(); - - void SetWebService(Deprecated::IWebService& webService); + void SetWebService(boost::shared_ptr<Deprecated::IWebService> webService); void SetOrthancBaseUrl(const std::string& baseUrl);
--- a/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -87,59 +87,73 @@ } - DicomSeriesVolumeSlicer::DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc) : - IVolumeSlicer(broker), - IObserver(broker), - loader_(broker, orthanc), + DicomSeriesVolumeSlicer::DicomSeriesVolumeSlicer() : quality_(SliceImageQuality_FullPng) { - loader_.RegisterObserverCallback( - new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryReadyMessage> - (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceGeometryErrorMessage> - (*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError)); + } - loader_.RegisterObserverCallback( - new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageReadyMessage> - (*this, &DicomSeriesVolumeSlicer::OnSliceImageReady)); - - loader_.RegisterObserverCallback( - new OrthancStone::Callable<DicomSeriesVolumeSlicer, OrthancSlicesLoader::SliceImageErrorMessage> - (*this, &DicomSeriesVolumeSlicer::OnSliceImageError)); + void DicomSeriesVolumeSlicer::Connect(boost::shared_ptr<OrthancApiClient> orthanc) + { + loader_.reset(new OrthancSlicesLoader(orthanc)); + Register<OrthancSlicesLoader::SliceGeometryReadyMessage>(*loader_, &DicomSeriesVolumeSlicer::OnSliceGeometryReady); + Register<OrthancSlicesLoader::SliceGeometryErrorMessage>(*loader_, &DicomSeriesVolumeSlicer::OnSliceGeometryError); + Register<OrthancSlicesLoader::SliceImageReadyMessage>(*loader_, &DicomSeriesVolumeSlicer::OnSliceImageReady); + Register<OrthancSlicesLoader::SliceImageErrorMessage>(*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<OrthancStone::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 +165,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_); } } }
--- a/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Layers/DicomSeriesVolumeSlicer.h Fri Nov 29 11:03:41 2019 +0100 @@ -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<DicomSeriesVolumeSlicer> //private OrthancSlicesLoader::ISliceLoaderObserver { public: @@ -79,13 +80,14 @@ private: class RendererFactory; - OrthancSlicesLoader loader_; + boost::shared_ptr<OrthancSlicesLoader> loader_; SliceImageQuality quality_; public: - DicomSeriesVolumeSlicer(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc); + DicomSeriesVolumeSlicer(); + void Connect(boost::shared_ptr<OrthancApiClient> 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<OrthancStone::Vector>& points,
--- a/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<OrthancStone::Point2D> > polygons_; +#else std::vector< std::pair<OrthancStone::Point2D, OrthancStone::Point2D> > 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<DicomStructureSetSlicer, StructureSetLoader::ContentChangedMessage> - (*this, &DicomStructureSetSlicer::OnStructureSetLoaded)); + Register<StructureSetLoader::ContentChangedMessage>(loader_, &DicomStructureSetSlicer::OnStructureSetLoaded); }
--- a/Framework/Deprecated/Layers/DicomStructureSetSlicer.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.h Fri Nov 29 11:03:41 2019 +0100 @@ -28,7 +28,7 @@ { class DicomStructureSetSlicer : public IVolumeSlicer, - public OrthancStone::IObserver + public OrthancStone::ObserverBase<DicomStructureSetSlicer> { private: class Renderer; @@ -42,8 +42,7 @@ } public: - DicomStructureSetSlicer(OrthancStone::MessageBroker& broker, - StructureSetLoader& loader); + DicomStructureSetSlicer(StructureSetLoader& loader); virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, const OrthancStone::CoordinateSystem3D& viewportPlane)
--- a/Framework/Deprecated/Layers/IVolumeSlicer.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Layers/IVolumeSlicer.h Fri Nov 29 11:03:41 2019 +0100 @@ -122,11 +122,6 @@ }; - IVolumeSlicer(OrthancStone::MessageBroker& broker) : - IObservable(broker) - { - } - virtual ~IVolumeSlicer() { }
--- a/Framework/Deprecated/SmartLoader.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/SmartLoader.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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> 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::auto_ptr<IVolumeSlicer> layerSource; + boost::shared_ptr<IVolumeSlicer> layerSource; std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame); SmartLoader::CachedSlice* cachedSlice = NULL; @@ -151,22 +142,23 @@ } else { - layerSource.reset(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); + layerSource.reset(new DicomSeriesVolumeSlicer); + dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->Connect(orthancApiClient_); dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady)); + Register<IVolumeSlicer::GeometryReadyMessage>(*layerSource, &SmartLoader::OnLayerGeometryReady); + Register<DicomSeriesVolumeSlicer::FrameReadyMessage>(*layerSource, &SmartLoader::OnFrameReady); + Register<IVolumeSlicer::LayerReadyMessage>(*layerSource, &SmartLoader::OnLayerReady); dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame); } // make sure that the widget registers the events before we trigger them if (sliceViewer.GetLayerCount() == layerIndex) { - sliceViewer.AddLayer(layerSource.release()); + 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> cachedSlice(new CachedSlice(IObserver::GetBroker())); + boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice); cachedSlice->slice_.reset(new Slice(instanceId, frame)); cachedSlice->status_ = CachedSliceStatus_ScheduledToLoad; std::string sliceKeyId = instanceId + ":" + boost::lexical_cast<std::string>(frame); @@ -199,12 +191,12 @@ cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice); - std::auto_ptr<IVolumeSlicer> layerSource(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); - + std::auto_ptr<IVolumeSlicer> layerSource(new DicomSeriesVolumeSlicer); + dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->Connect(orthancApiClient_); dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->SetImageQuality(imageQuality_); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::GeometryReadyMessage>(*this, &SmartLoader::OnLayerGeometryReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, DicomSeriesVolumeSlicer::FrameReadyMessage>(*this, &SmartLoader::OnFrameReady)); - layerSource->RegisterObserverCallback(new OrthancStone::Callable<SmartLoader, IVolumeSlicer::LayerReadyMessage>(*this, &SmartLoader::OnLayerReady)); + Register<IVolumeSlicer::GeometryReadyMessage>(*layerSource, &SmartLoader::OnLayerGeometryReady); + Register<DicomSeriesVolumeSlicer::FrameReadyMessage>(*layerSource, &SmartLoader::OnFrameReady); + Register<IVolumeSlicer::LayerReadyMessage>(*layerSource, &SmartLoader::OnLayerReady); dynamic_cast<DicomSeriesVolumeSlicer*>(layerSource.get())->LoadFrame(instanceId, frame); // keep a ref to the VolumeSlicer until the slice is fully loaded and saved to cache @@ -235,7 +227,7 @@ LOG(WARNING) << "Geometry ready: " << sliceKeyId; - boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice(IObserver::GetBroker())); + boost::shared_ptr<CachedSlice> 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> cachedSlice(new CachedSlice(IObserver::GetBroker())); + boost::shared_ptr<CachedSlice> cachedSlice(new CachedSlice); cachedSlice->image_.reset(Orthanc::Image::Clone(message.GetFrame())); cachedSlice->effectiveQuality_ = message.GetImageQuality(); cachedSlice->slice_.reset(message.GetSlice().Clone());
--- a/Framework/Deprecated/SmartLoader.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/SmartLoader.h Fri Nov 29 11:03:41 2019 +0100 @@ -30,7 +30,7 @@ { class SliceViewerWidget; - class SmartLoader : public OrthancStone::IObservable, public OrthancStone::IObserver + class SmartLoader : public OrthancStone::IObservable, public OrthancStone::ObserverBase<SmartLoader> { class CachedSlice; @@ -42,10 +42,10 @@ PreloadingInstances preloadingInstances_; SliceImageQuality imageQuality_; - OrthancApiClient& orthancApiClient_; + boost::shared_ptr<OrthancApiClient> orthancApiClient_; public: - SmartLoader(OrthancStone::MessageBroker& broker, OrthancApiClient& orthancApiClient); // TODO: add maxPreloadStorageSizeInBytes + SmartLoader(boost::shared_ptr<OrthancApiClient> orthancApiClient); // TODO: add maxPreloadStorageSizeInBytes // void PreloadStudy(const std::string studyId); // void PreloadSeries(const std::string seriesId);
--- a/Framework/Deprecated/Toolbox/BaseWebService.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Toolbox/BaseWebService.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -97,9 +97,9 @@ GetAsyncInternal(uri, headers, new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestSuccessMessage> - (*this, &BaseWebService::CacheAndNotifyHttpSuccess), + (GetSharedObserver(), &BaseWebService::CacheAndNotifyHttpSuccess), new OrthancStone::Callable<BaseWebService, IWebService::HttpRequestErrorMessage> - (*this, &BaseWebService::NotifyHttpError), + (GetSharedObserver(), &BaseWebService::NotifyHttpError), timeoutInSeconds); } else
--- a/Framework/Deprecated/Toolbox/BaseWebService.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Toolbox/BaseWebService.h Fri Nov 29 11:03:41 2019 +0100 @@ -22,6 +22,7 @@ #pragma once #include "IWebService.h" +#include "../../Messages/ObserverBase.h" #include <string> #include <map> @@ -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<BaseWebService> { public: class CachedHttpRequestSuccessMessage @@ -90,10 +91,7 @@ std::deque<std::string> orderedCacheKeys_; public: - - BaseWebService(OrthancStone::MessageBroker& broker) : - IWebService(broker), - IObserver(broker), + BaseWebService() : cacheEnabled_(false), cacheCurrentSize_(0), cacheMaxSize_(100*1024*1024)
--- a/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h Fri Nov 29 11:03:41 2019 +0100 @@ -35,22 +35,12 @@ // The IDelayedCall executes a callback after a delay (equivalent to timeout() function in javascript). class IDelayedCallExecutor : public boost::noncopyable { - protected: - OrthancStone::MessageBroker& broker_; - public: ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(__FILE__, __LINE__, TimeoutMessage); - IDelayedCallExecutor(OrthancStone::MessageBroker& broker) : - broker_(broker) - { - } - - virtual ~IDelayedCallExecutor() { } - virtual void Schedule(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback, unsigned int timeoutInMs = 1000) = 0;
--- a/Framework/Deprecated/Toolbox/IWebService.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Toolbox/IWebService.h Fri Nov 29 11:03:41 2019 +0100 @@ -40,9 +40,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<std::string, std::string> HttpHeaders; @@ -138,12 +135,6 @@ }; - IWebService(OrthancStone::MessageBroker& broker) : - broker_(broker) - { - } - - virtual ~IWebService() { }
--- a/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -73,7 +73,6 @@ std::auto_ptr< OrthancStone::MessageHandler<BinaryResponseReadyMessage> > binaryHandler_; std::auto_ptr< OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage> > failureHandler_; std::auto_ptr< Orthanc::IDynamicObject > userPayload_; - OrthancStone::MessageBroker& broker_; void NotifyConversionError(const IWebService::HttpRequestSuccessMessage& message) const { if (failureHandler_.get() != NULL) @@ -84,14 +83,12 @@ } public: - WebServicePayload(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<EmptyResponseReadyMessage>* handler, + WebServicePayload(OrthancStone::MessageHandler<EmptyResponseReadyMessage>* handler, OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, Orthanc::IDynamicObject* userPayload) : emptyHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload), - broker_(broker) + userPayload_(userPayload) { if (handler == NULL) @@ -100,14 +97,12 @@ } } - WebServicePayload(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<BinaryResponseReadyMessage>* handler, + WebServicePayload(OrthancStone::MessageHandler<BinaryResponseReadyMessage>* handler, OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, Orthanc::IDynamicObject* userPayload) : binaryHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload), - broker_(broker) + userPayload_(userPayload) { if (handler == NULL) { @@ -115,14 +110,12 @@ } } - WebServicePayload(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<JsonResponseReadyMessage>* handler, + WebServicePayload(OrthancStone::MessageHandler<JsonResponseReadyMessage>* handler, OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureHandler, Orthanc::IDynamicObject* userPayload) : jsonHandler_(handler), failureHandler_(failureHandler), - userPayload_(userPayload), - broker_(broker) + userPayload_(userPayload) { if (handler == NULL) { @@ -134,35 +127,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 +166,8 @@ }; - OrthancApiClient::OrthancApiClient(OrthancStone::MessageBroker& broker, - IWebService& web, + OrthancApiClient::OrthancApiClient(IWebService& web, const std::string& baseUrl) : - IObservable(broker), - IObserver(broker), web_(web), baseUrl_(baseUrl) { @@ -202,11 +183,11 @@ IWebService::HttpHeaders emptyHeaders; web_.GetAsync(baseUrl_ + uri, emptyHeaders, - new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), + new WebServicePayload(successCallback, failureCallback, payload), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> - (*this, &OrthancApiClient::NotifyHttpSuccess), + (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> - (*this, &OrthancApiClient::NotifyHttpError)); + (GetSharedObserver(), &OrthancApiClient::NotifyHttpError)); } @@ -232,11 +213,11 @@ // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str()); web_.GetAsync(baseUrl_ + uri, headers, - new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), + new WebServicePayload(successCallback, failureCallback, payload), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> - (*this, &OrthancApiClient::NotifyHttpSuccess), + (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> - (*this, &OrthancApiClient::NotifyHttpError)); + (GetSharedObserver(), &OrthancApiClient::NotifyHttpError)); } @@ -248,11 +229,11 @@ Orthanc::IDynamicObject* payload) { web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), + new WebServicePayload(successCallback, failureCallback, payload), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> - (*this, &OrthancApiClient::NotifyHttpSuccess), + (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> - (*this, &OrthancApiClient::NotifyHttpError)); + (GetSharedObserver(), &OrthancApiClient::NotifyHttpError)); } @@ -271,11 +252,11 @@ Orthanc::IDynamicObject* payload /* takes ownership */) { web_.PostAsync(baseUrl_ + uri, IWebService::HttpHeaders(), body, - new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), + new WebServicePayload(successCallback, failureCallback, payload), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> - (*this, &OrthancApiClient::NotifyHttpSuccess), + (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> - (*this, &OrthancApiClient::NotifyHttpError)); + (GetSharedObserver(), &OrthancApiClient::NotifyHttpError)); } void OrthancApiClient::PostJsonAsyncExpectJson( @@ -318,11 +299,11 @@ Orthanc::IDynamicObject* payload) { web_.DeleteAsync(baseUrl_ + uri, IWebService::HttpHeaders(), - new WebServicePayload(IObservable::GetBroker(), successCallback, failureCallback, payload), + new WebServicePayload(successCallback, failureCallback, payload), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage> - (*this, &OrthancApiClient::NotifyHttpSuccess), + (GetSharedObserver(), &OrthancApiClient::NotifyHttpSuccess), new OrthancStone::Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage> - (*this, &OrthancApiClient::NotifyHttpError)); + (GetSharedObserver(), &OrthancApiClient::NotifyHttpError)); }
--- a/Framework/Deprecated/Toolbox/OrthancApiClient.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Toolbox/OrthancApiClient.h Fri Nov 29 11:03:41 2019 +0100 @@ -25,13 +25,13 @@ #include <json/json.h> #include "IWebService.h" -#include "../../Messages/IObservable.h" +#include "../../Messages/ObserverBase.h" namespace Deprecated { class OrthancApiClient : public OrthancStone::IObservable, - public OrthancStone::IObserver + public OrthancStone::ObserverBase<OrthancApiClient> { public: class JsonResponseReadyMessage : public OrthancStone::IMessage @@ -157,8 +157,7 @@ std::string baseUrl_; public: - OrthancApiClient(OrthancStone::MessageBroker& broker, - IWebService& web, + OrthancApiClient(IWebService& web, const std::string& baseUrl); virtual ~OrthancApiClient()
--- a/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -639,10 +639,7 @@ } - OrthancSlicesLoader::OrthancSlicesLoader(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc) : - OrthancStone::IObservable(broker), - OrthancStone::IObserver(broker), + OrthancSlicesLoader::OrthancSlicesLoader(boost::shared_ptr<OrthancApiClient> orthanc) : orthanc_(orthanc), state_(State_Initialization) { @@ -658,10 +655,10 @@ else { state_ = State_LoadingGeometry; - orthanc_.GetJsonAsync("/series/" + seriesId + "/instances-tags", - new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSeriesGeometry), - new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError), - NULL); + orthanc_->GetJsonAsync("/series/" + seriesId + "/instances-tags", + new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &OrthancSlicesLoader::ParseSeriesGeometry), + new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(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<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseInstanceGeometry), - new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError), - Operation::DownloadInstanceGeometry(instanceId)); + orthanc_->GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c", + new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &OrthancSlicesLoader::ParseInstanceGeometry), + new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(GetSharedObserver(), &OrthancSlicesLoader::OnGeometryError), + Operation::DownloadInstanceGeometry(instanceId)); } } @@ -696,10 +693,10 @@ { state_ = State_LoadingGeometry; - orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags", - new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseFrameGeometry), - new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError), - Operation::DownloadFrameGeometry(instanceId, frame)); + orthanc_->GetJsonAsync("/instances/" + instanceId + "/tags", + new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &OrthancSlicesLoader::ParseFrameGeometry), + new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(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<OrthancSlicesLoader, - OrthancApiClient::BinaryResponseReadyMessage> - (*this, &OrthancSlicesLoader::ParseSliceImagePng), - new OrthancStone::Callable<OrthancSlicesLoader, - IWebService::HttpRequestErrorMessage> - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceImage( - static_cast<unsigned int>(index), slice, SliceImageQuality_FullPng)); -} + orthanc_->GetBinaryAsync(uri, "image/png", + new OrthancStone::Callable<OrthancSlicesLoader, + OrthancApiClient::BinaryResponseReadyMessage> + (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceImagePng), + new OrthancStone::Callable<OrthancSlicesLoader, + IWebService::HttpRequestErrorMessage> + (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage( + static_cast<unsigned int>(index), slice, SliceImageQuality_FullPng)); + } void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice, size_t index) { std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + - boost::lexical_cast<std::string>(slice.GetFrame())); + boost::lexical_cast<std::string>(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<OrthancSlicesLoader, - OrthancApiClient::BinaryResponseReadyMessage> - (*this, &OrthancSlicesLoader::ParseSliceImagePam), - new OrthancStone::Callable<OrthancSlicesLoader, - IWebService::HttpRequestErrorMessage> - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceImage(static_cast<unsigned int>(index), - slice, SliceImageQuality_FullPam)); + orthanc_->GetBinaryAsync(uri, "image/x-portable-arbitrarymap", + new OrthancStone::Callable<OrthancSlicesLoader, + OrthancApiClient::BinaryResponseReadyMessage> + (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceImagePam), + new OrthancStone::Callable<OrthancSlicesLoader, + IWebService::HttpRequestErrorMessage> + (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage(static_cast<unsigned int>(index), + slice, SliceImageQuality_FullPam)); } @@ -849,15 +846,15 @@ "-" + slice.GetOrthancInstanceId() + "_" + boost::lexical_cast<std::string>(slice.GetFrame())); - orthanc_.GetJsonAsync(uri, - new OrthancStone::Callable<OrthancSlicesLoader, - OrthancApiClient::JsonResponseReadyMessage> - (*this, &OrthancSlicesLoader::ParseSliceImageJpeg), - new OrthancStone::Callable<OrthancSlicesLoader, - IWebService::HttpRequestErrorMessage> - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceImage( - static_cast<unsigned int>(index), slice, quality)); + orthanc_->GetJsonAsync(uri, + new OrthancStone::Callable<OrthancSlicesLoader, + OrthancApiClient::JsonResponseReadyMessage> + (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceImageJpeg), + new OrthancStone::Callable<OrthancSlicesLoader, + IWebService::HttpRequestErrorMessage> + (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage( + static_cast<unsigned int>(index), slice, quality)); } @@ -890,15 +887,15 @@ { std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz"); - orthanc_.GetBinaryAsync(uri, IWebService::HttpHeaders(), - new OrthancStone::Callable<OrthancSlicesLoader, - OrthancApiClient::BinaryResponseReadyMessage> - (*this, &OrthancSlicesLoader::ParseSliceRawImage), - new OrthancStone::Callable<OrthancSlicesLoader, - IWebService::HttpRequestErrorMessage> - (*this, &OrthancSlicesLoader::OnSliceImageError), - Operation::DownloadSliceRawImage( - static_cast<unsigned int>(index), slice)); + orthanc_->GetBinaryAsync(uri, IWebService::HttpHeaders(), + new OrthancStone::Callable<OrthancSlicesLoader, + OrthancApiClient::BinaryResponseReadyMessage> + (GetSharedObserver(), &OrthancSlicesLoader::ParseSliceRawImage), + new OrthancStone::Callable<OrthancSlicesLoader, + IWebService::HttpRequestErrorMessage> + (GetSharedObserver(), &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceRawImage( + static_cast<unsigned int>(index), slice)); } } }
--- a/Framework/Deprecated/Toolbox/OrthancSlicesLoader.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.h Fri Nov 29 11:03:41 2019 +0100 @@ -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<OrthancSlicesLoader> { public: ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SliceGeometryReadyMessage, OrthancSlicesLoader); @@ -143,7 +146,7 @@ class Operation; - OrthancApiClient& orthanc_; + boost::shared_ptr<OrthancApiClient> 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<OrthancApiClient> orthancApi); void ScheduleLoadSeries(const std::string& seriesId);
--- a/Framework/Deprecated/Viewport/IViewport.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Viewport/IViewport.h Fri Nov 29 11:03:41 2019 +0100 @@ -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;
--- a/Framework/Deprecated/Viewport/WidgetViewport.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Viewport/WidgetViewport.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<IWidget> 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; }
--- a/Framework/Deprecated/Viewport/WidgetViewport.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Viewport/WidgetViewport.h Fri Nov 29 11:03:41 2019 +0100 @@ -31,7 +31,7 @@ class WidgetViewport : public IViewport { private: - std::auto_ptr<IWidget> centralWidget_; + boost::shared_ptr<IWidget> centralWidget_; IStatusBar* statusBar_; std::auto_ptr<IMouseTracker> mouseTracker_; bool isMouseOver_; @@ -41,13 +41,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<IWidget> widget); virtual void NotifyBackgroundChanged();
--- a/Framework/Deprecated/Volumes/ISlicedVolume.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Volumes/ISlicedVolume.h Fri Nov 29 11:03:41 2019 +0100 @@ -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;
--- a/Framework/Deprecated/Volumes/IVolumeLoader.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Volumes/IVolumeLoader.h Fri Nov 29 11:03:41 2019 +0100 @@ -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) - { - } }; }
--- a/Framework/Deprecated/Volumes/StructureSetLoader.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Volumes/StructureSetLoader.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnLookupCompleted)); + new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(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<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnReferencedSliceLoaded)); + new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &StructureSetLoader::OnReferencedSliceLoaded)); } @@ -97,7 +94,7 @@ else { orthanc_.GetJsonAsync("/instances/" + instance + "/tags?ignore-length=3006-0050", - new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnStructureSetLoaded)); + new OrthancStone::Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(GetSharedObserver(), &StructureSetLoader::OnStructureSetLoaded)); } }
--- a/Framework/Deprecated/Volumes/StructureSetLoader.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Volumes/StructureSetLoader.h Fri Nov 29 11:03:41 2019 +0100 @@ -21,6 +21,7 @@ #pragma once +#include "../../Messages/ObserverBase.h" #include "../../Toolbox/DicomStructureSet.h" #include "../Toolbox/OrthancApiClient.h" #include "IVolumeLoader.h" @@ -29,7 +30,7 @@ { class StructureSetLoader : public IVolumeLoader, - public OrthancStone::IObserver + public OrthancStone::ObserverBase<StructureSetLoader> { private: OrthancApiClient& orthanc_; @@ -42,8 +43,7 @@ void OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message); public: - StructureSetLoader(OrthancStone::MessageBroker& broker, - OrthancApiClient& orthanc); + StructureSetLoader(OrthancApiClient& orthanc); void ScheduleLoadInstance(const std::string& instance);
--- a/Framework/Deprecated/Widgets/LayoutWidget.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Widgets/LayoutWidget.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -85,14 +85,14 @@ class LayoutWidget::ChildWidget : public boost::noncopyable { private: - std::auto_ptr<IWidget> widget_; + boost::shared_ptr<IWidget> widget_; int left_; int top_; unsigned int width_; unsigned int height_; public: - ChildWidget(IWidget* widget) : + ChildWidget(boost::shared_ptr<IWidget> widget) : widget_(widget) { assert(widget != NULL); @@ -354,7 +354,7 @@ } - IWidget& LayoutWidget::AddWidget(IWidget* widget) // Takes ownership + void LayoutWidget::AddWidget(boost::shared_ptr<IWidget> widget) // Takes ownership { if (widget == NULL) { @@ -375,8 +375,6 @@ { hasAnimation_ = true; } - - return *widget; }
--- a/Framework/Deprecated/Widgets/LayoutWidget.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Widgets/LayoutWidget.h Fri Nov 29 11:03:41 2019 +0100 @@ -94,7 +94,7 @@ return paddingInternal_; } - IWidget& AddWidget(IWidget* widget); // Takes ownership + void AddWidget(boost::shared_ptr<IWidget> widget); virtual void SetStatusBar(IStatusBar& statusBar);
--- a/Framework/Deprecated/Widgets/SliceViewerWidget.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Widgets/SliceViewerWidget.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -250,7 +250,7 @@ { index = found->second; assert(index < layers_.size() && - layers_[index] == &layer); + layers_[index].get() == &layer); return true; } } @@ -364,42 +364,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<SliceViewerWidget, IVolumeSlicer::GeometryReadyMessage> - (*this, &SliceViewerWidget::OnGeometryReady)); - // currently ignore errors layer->RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::GeometryErrorMessage>(*this, &SliceViewerWidget::...)); - layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::SliceContentChangedMessage> - (*this, &SliceViewerWidget::OnSliceChanged)); - layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::ContentChangedMessage> - (*this, &SliceViewerWidget::OnContentChanged)); - layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerReadyMessage> - (*this, &SliceViewerWidget::OnLayerReady)); - layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerErrorMessage> - (*this, &SliceViewerWidget::OnLayerError)); + // currently ignoring errors of type IVolumeSlicer::GeometryErrorMessage + + Register<IVolumeSlicer::GeometryReadyMessage>(layer, &SliceViewerWidget::OnGeometryReady); + Register<IVolumeSlicer::SliceContentChangedMessage>(layer, &SliceViewerWidget::OnSliceChanged); + Register<IVolumeSlicer::ContentChangedMessage>(layer, &SliceViewerWidget::OnContentChanged); + Register<IVolumeSlicer::LayerReadyMessage>(layer, &SliceViewerWidget::OnLayerReady); + Register<IVolumeSlicer::LayerErrorMessage>(layer, &SliceViewerWidget::OnLayerError); } - size_t SliceViewerWidget::AddLayer(IVolumeSlicer* layer) // Takes ownership + size_t SliceViewerWidget::AddLayer(boost::shared_ptr<IVolumeSlicer> layer) { if (layer == NULL) { @@ -409,7 +394,7 @@ size_t index = layers_.size(); layers_.push_back(layer); styles_.push_back(RenderStyle()); - layersIndex_[layer] = index; + layersIndex_[layer.get()] = index; ResetPendingScene(); @@ -421,7 +406,8 @@ } - void SliceViewerWidget::ReplaceLayer(size_t index, IVolumeSlicer* layer) // Takes ownership + void SliceViewerWidget::ReplaceLayer(size_t index, + boost::shared_ptr<IVolumeSlicer> layer) { if (layer == NULL) { @@ -433,9 +419,8 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - delete layers_[index]; layers_[index] = layer; - layersIndex_[layer] = index; + layersIndex_[layer.get()] = index; ResetPendingScene(); @@ -452,13 +437,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();
--- a/Framework/Deprecated/Widgets/SliceViewerWidget.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Deprecated/Widgets/SliceViewerWidget.h Fri Nov 29 11:03:41 2019 +0100 @@ -24,7 +24,7 @@ #include "WorldSceneWidget.h" #include "../Layers/IVolumeSlicer.h" #include "../../Toolbox/Extent2D.h" -#include "../../Messages/IObserver.h" +#include "../../Messages/ObserverBase.h" #include <map> @@ -32,7 +32,7 @@ { class SliceViewerWidget : public WorldSceneWidget, - public OrthancStone::IObserver, + public OrthancStone::ObserverBase<SliceViewerWidget>, public OrthancStone::IObservable { public: @@ -72,7 +72,7 @@ bool started_; LayersIndex layersIndex_; - std::vector<IVolumeSlicer*> layers_; + std::vector<boost::shared_ptr<IVolumeSlicer> > layers_; std::vector<RenderStyle> styles_; OrthancStone::CoordinateSystem3D plane_; std::auto_ptr<Scene> currentScene_; @@ -100,8 +100,7 @@ void ResetChangedLayers(); public: - SliceViewerWidget(OrthancStone::MessageBroker& broker, - const std::string& name); + SliceViewerWidget(const std::string& name); virtual OrthancStone::Extent2D GetSceneExtent(); @@ -120,11 +119,13 @@ void InvalidateLayer(size_t layer); public: - virtual ~SliceViewerWidget(); + virtual ~SliceViewerWidget() + { + } - size_t AddLayer(IVolumeSlicer* layer); // Takes ownership + size_t AddLayer(boost::shared_ptr<IVolumeSlicer> layer); - void ReplaceLayer(size_t layerIndex, IVolumeSlicer* layer); // Takes ownership + void ReplaceLayer(size_t layerIndex, boost::shared_ptr<IVolumeSlicer> layer); // Takes ownership void RemoveLayer(size_t layerIndex);
--- a/Framework/Loaders/DicomStructureSetLoader.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Loaders/DicomStructureSetLoader.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -144,7 +144,7 @@ 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()); } } @@ -231,7 +231,7 @@ 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()); } } @@ -347,7 +347,6 @@ DicomStructureSetLoader::DicomStructureSetLoader(IOracle& oracle, IObservable& oracleObservable) : LoaderStateMachine(oracle, oracleObservable), - IObservable(oracleObservable.GetBroker()), revision_(0), countProcessedInstances_(0), countReferencedInstances_(0), @@ -383,7 +382,7 @@ 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()); } }
--- a/Framework/Loaders/LoaderCache.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Loaders/LoaderCache.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -65,7 +65,7 @@ } #else - LoaderCache::LoaderCache(ThreadedOracle& oracle, LockingEmitter& lockingEmitter) + LoaderCache::LoaderCache(ThreadedOracle& oracle, Deprecated::LockingEmitter& lockingEmitter) : oracle_(oracle) , lockingEmitter_(lockingEmitter) { @@ -98,7 +98,7 @@ #if ORTHANC_ENABLE_WASM == 1 loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, oracle_)); #else - LockingEmitter::WriterLock lock(lockingEmitter_); + Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); loader.reset(new OrthancSeriesVolumeProgressiveLoader(volumeImage, oracle_, lock.GetOracleObservable())); #endif // LOG(TRACE) << "LoaderCache::GetSeriesVolumeProgressiveLoader : loader = " << loader.get(); @@ -167,10 +167,8 @@ #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())); + Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); + loader.reset(new OrthancMultiframeVolumeLoader(volumeImage, oracle_, lock.GetOracleObservable())); #endif loader->LoadInstance(instanceUuid); } @@ -270,7 +268,7 @@ #if ORTHANC_ENABLE_WASM == 1 loader.reset(new DicomStructureSetLoader(oracle_, oracle_)); #else - LockingEmitter::WriterLock lock(lockingEmitter_); + Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); loader.reset(new DicomStructureSetLoader(oracle_, lock.GetOracleObservable())); #endif loader->LoadInstance(inInstanceUuid, initiallyVisibleStructures); @@ -366,7 +364,7 @@ void LoaderCache::ClearCache() { #if ORTHANC_ENABLE_WASM != 1 - LockingEmitter::WriterLock lock(lockingEmitter_); + Deprecated::LockingEmitter::WriterLock lock(lockingEmitter_); #endif //#ifndef NDEBUG
--- a/Framework/Loaders/LoaderCache.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Loaders/LoaderCache.h Fri Nov 29 11:03:41 2019 +0100 @@ -43,7 +43,10 @@ class WebAssemblyOracle; #else class ThreadedOracle; - class LockingEmitter; + namespace Deprecated + { + class LockingEmitter; + } #endif class LoaderCache @@ -52,7 +55,7 @@ #if ORTHANC_ENABLE_WASM == 1 LoaderCache(WebAssemblyOracle& oracle); #else - LoaderCache(ThreadedOracle& oracle, LockingEmitter& lockingEmitter); + LoaderCache(ThreadedOracle& oracle, Deprecated::LockingEmitter& lockingEmitter); #endif boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> @@ -87,7 +90,7 @@ WebAssemblyOracle& oracle_; #else ThreadedOracle& oracle_; - LockingEmitter& lockingEmitter_; + Deprecated::LockingEmitter& lockingEmitter_; #endif std::map<std::string, boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> >
--- a/Framework/Loaders/LoaderStateMachine.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Loaders/LoaderStateMachine.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -43,11 +43,11 @@ } - void LoaderStateMachine::Schedule(OracleCommandWithPayload* command) + void LoaderStateMachine::Schedule(OracleCommandBase* command) { LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::Schedule()"; - std::auto_ptr<OracleCommandWithPayload> protection(command); + std::auto_ptr<OracleCommandBase> protection(command); if (command == NULL) { @@ -97,7 +97,8 @@ ") < simultaneousDownloads_ (" << simultaneousDownloads_ << ") --> will Schedule command addr " << std::hex << nextCommand << std::dec; - oracle_.Schedule(*this, nextCommand); + boost::shared_ptr<IObserver> observer(GetSharedObserver()); + oracle_.Schedule(observer, nextCommand); pendingCommands_.pop_front(); activeCommands_++; @@ -136,7 +137,6 @@ template <typename T> 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_; } @@ -159,35 +159,22 @@ LoaderStateMachine::LoaderStateMachine(IOracle& oracle, IObservable& oracleObservable) : - IObserver(oracleObservable.GetBroker()), oracle_(oracle), - oracleObservable_(oracleObservable), active_(false), simultaneousDownloads_(4), activeCommands_(0) { LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::LoaderStateMachine()"; - oracleObservable.RegisterObserverCallback( - new Callable<LoaderStateMachine, OrthancRestApiCommand::SuccessMessage> - (*this, &LoaderStateMachine::HandleSuccessMessage)); - - oracleObservable.RegisterObserverCallback( - new Callable<LoaderStateMachine, GetOrthancImageCommand::SuccessMessage> - (*this, &LoaderStateMachine::HandleSuccessMessage)); - - oracleObservable.RegisterObserverCallback( - new Callable<LoaderStateMachine, GetOrthancWebViewerJpegCommand::SuccessMessage> - (*this, &LoaderStateMachine::HandleSuccessMessage)); - - oracleObservable.RegisterObserverCallback( - new Callable<LoaderStateMachine, OracleCommandExceptionMessage> - (*this, &LoaderStateMachine::HandleExceptionMessage)); + // TODO => Move this out of constructor + Register<OrthancRestApiCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage); + Register<GetOrthancImageCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage); + Register<GetOrthancWebViewerJpegCommand::SuccessMessage>(oracleObservable, &LoaderStateMachine::HandleSuccessMessage); + Register<OracleCommandExceptionMessage>(oracleObservable, &LoaderStateMachine::HandleExceptionMessage); } LoaderStateMachine::~LoaderStateMachine() { - oracleObservable_.Unregister(this); LOG(TRACE) << "LoaderStateMachine(" << std::hex << this << std::dec << ")::~LoaderStateMachine()"; Clear(); }
--- a/Framework/Loaders/LoaderStateMachine.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Loaders/LoaderStateMachine.h Fri Nov 29 11:03:41 2019 +0100 @@ -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" @@ -41,7 +41,7 @@ rest once slots become available. It is used, a.o., by the OrtancMultiframeVolumeLoader class. */ - class LoaderStateMachine : public IObserver + class LoaderStateMachine : public ObserverBase<LoaderStateMachine> { protected: class State : public Orthanc::IDynamicObject @@ -60,7 +60,7 @@ { } - void Schedule(OracleCommandWithPayload* command) const + void Schedule(OracleCommandBase* command) const { that_.Schedule(command); } @@ -78,7 +78,7 @@ virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message); }; - void Schedule(OracleCommandWithPayload* command); + void Schedule(OracleCommandBase* command); void Start(); @@ -95,7 +95,6 @@ typedef std::list<IOracleCommand*> PendingCommands; IOracle& oracle_; - IObservable& oracleObservable_; bool active_; unsigned int simultaneousDownloads_; PendingCommands pendingCommands_;
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -101,7 +101,7 @@ std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" + Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format()); - command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release())); + command->AcquirePayload(new LoadRTDoseGeometry(loader, dicom.release())); Schedule(command.release()); } @@ -175,7 +175,7 @@ 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 @@ -345,7 +345,6 @@ IOracle& oracle, IObservable& oracleObservable) : LoaderStateMachine(oracle, oracleObservable), - IObservable(oracleObservable.GetBroker()), volume_(volume), pixelDataLoaded_(false) { @@ -370,14 +369,14 @@ std::auto_ptr<OrthancRestApiCommand> command(new 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::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax"); - command->SetPayload(new LoadTransferSyntax(*this)); + command->AcquirePayload(new LoadTransferSyntax(*this)); Schedule(command.release()); } }
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.h Fri Nov 29 11:03:41 2019 +0100 @@ -30,8 +30,7 @@ { class OrthancMultiframeVolumeLoader : public LoaderStateMachine, - public IObservable, - public IGeometryProvider + public IObservable { private: class LoadRTDoseGeometry; @@ -57,8 +56,8 @@ void SetUncompressedPixelData(const std::string& pixelData); - virtual bool HasGeometry() const ORTHANC_OVERRIDE; - virtual const VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE; + bool HasGeometry() const; + const VolumeImageGeometry& GetImageGeometry() const; public: OrthancMultiframeVolumeLoader(boost::shared_ptr<DicomVolumeImage> volume,
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -185,18 +185,27 @@ CheckVolume(); - const double spacingZ = slices.ComputeSpacingBetweenSlices(); - LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; + double spacingZ; + + if (slices.ComputeSpacingBetweenSlices(spacingZ)) + { + LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; - const DicomInstanceParameters& parameters = *slices_[0]; + const DicomInstanceParameters& parameters = *slices_[0]; - geometry_.reset(new VolumeImageGeometry); - geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(), - parameters.GetImageInformation().GetHeight(), - static_cast<unsigned int>(slices.GetSlicesCount())); - geometry_->SetAxialGeometry(slices.GetSliceGeometry(0)); - geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(), - parameters.GetPixelSpacingY(), spacingZ); + geometry_.reset(new VolumeImageGeometry); + geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(), + parameters.GetImageInformation().GetHeight(), + static_cast<unsigned int>(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"); + } } } @@ -237,8 +246,9 @@ } - static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command) + static unsigned int GetSliceIndexPayload(const OracleCommandBase& command) { + assert(command.HasPayload()); return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue(); } @@ -261,7 +271,7 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - std::auto_ptr<OracleCommandWithPayload> command; + std::auto_ptr<OracleCommandBase> command; if (quality == BEST_QUALITY) { @@ -291,8 +301,10 @@ command.reset(tmp.release()); } - command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex)); - oracle_.Schedule(*this, command.release()); + command->AcquirePayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex)); + + boost::shared_ptr<IObserver> observer(GetSharedObserver()); + oracle_.Schedule(observer, command.release()); } else { @@ -400,7 +412,7 @@ { unsigned int quality; - switch (message.GetOrigin().GetQuality()) + switch (dynamic_cast<const GetOrthancWebViewerJpegCommand&>(message.GetOrigin()).GetQuality()) { case 50: quality = LOW_QUALITY; @@ -421,32 +433,26 @@ OrthancSeriesVolumeProgressiveLoader::OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& 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) { - oracleObservable.RegisterObserverCallback( - new Callable<OrthancSeriesVolumeProgressiveLoader, OrthancRestApiCommand::SuccessMessage> - (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry)); + // TODO => Move this out of constructor + Register<OrthancRestApiCommand::SuccessMessage> + (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry); - oracleObservable.RegisterObserverCallback( - new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancImageCommand::SuccessMessage> - (*this, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent)); + Register<GetOrthancImageCommand::SuccessMessage> + (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent); - oracleObservable.RegisterObserverCallback( - new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancWebViewerJpegCommand::SuccessMessage> - (*this, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent)); + Register<GetOrthancWebViewerJpegCommand::SuccessMessage> + (oracleObservable, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent); } OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader() { - oracleObservable_.Unregister(this); LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::~OrthancSeriesVolumeProgressiveLoader()"; } @@ -485,7 +491,8 @@ command->SetUri("/series/" + seriesId + "/instances-tags"); // LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries about to call oracle_.Schedule"; - oracle_.Schedule(*this, command.release()); + boost::shared_ptr<IObserver> observer(GetSharedObserver()); + oracle_.Schedule(observer, command.release()); // LOG(TRACE) << "OrthancSeriesVolumeProgressiveLoader::LoadSeries called oracle_.Schedule"; } }
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Fri Nov 29 11:03:41 2019 +0100 @@ -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" @@ -42,10 +42,9 @@ is stored in a Dicom series. */ class OrthancSeriesVolumeProgressiveLoader : - public IObserver, + public ObserverBase<OrthancSeriesVolumeProgressiveLoader>, public IObservable, - public IVolumeSlicer, - public IGeometryProvider + public IVolumeSlicer { private: static const unsigned int LOW_QUALITY = 0; @@ -106,7 +105,6 @@ void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message); IOracle& oracle_; - IObservable& oracleObservable_; bool active_; unsigned int simultaneousDownloads_; SeriesGeometry seriesGeometry_; @@ -141,7 +139,7 @@ subscribing, for instance if they are created or listening only AFTER the "geometry loaded" message is broadcast */ - bool HasGeometry() const ORTHANC_OVERRIDE + bool HasGeometry() const { return seriesGeometry_.HasGeometry(); } @@ -149,7 +147,7 @@ /** Same remark as HasGeometry */ - const VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE + const VolumeImageGeometry& GetImageGeometry() const { return seriesGeometry_.GetImageGeometry(); }
--- a/Framework/Messages/ICallable.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Messages/ICallable.h Fri Nov 29 11:03:41 2019 +0100 @@ -22,20 +22,20 @@ #pragma once #include "IMessage.h" +#include "IObserver.h" #include <Core/Logging.h> #include <boost/noncopyable.hpp> +#include <boost/weak_ptr.hpp> #include <string> -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: @@ -47,11 +47,14 @@ virtual const MessageIdentifier& GetMessageIdentifier() = 0; - virtual IObserver* GetObserver() const = 0; + // TODO - Is this needed? + virtual boost::weak_ptr<IObserver> GetObserver() const = 0; }; + + // TODO - Remove this class template <typename TMessage> - class MessageHandler: public ICallable + class MessageHandler : public ICallable { }; @@ -61,47 +64,28 @@ class Callable : public MessageHandler<TMessage> { private: - typedef void (TObserver::* MemberFunction) (const TMessage&); + typedef void (TObserver::* MemberMethod) (const TMessage&); - TObserver& observer_; - MemberFunction function_; - std::string observerFingerprint_; + boost::weak_ptr<IObserver> observer_; + MemberMethod function_; public: - Callable(TObserver& observer, - MemberFunction function) : + Callable(boost::shared_ptr<TObserver> observer, + MemberMethod function) : observer_(observer), - function_(function), - observerFingerprint_(observer.GetFingerprint()) - { - } - - void ApplyInternal(const TMessage& message) + function_(function) { - std::string 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<const TMessage&>(message)); + boost::shared_ptr<IObserver> lock(observer_); + if (lock) + { + TObserver& observer = dynamic_cast<TObserver&>(*lock); + const TMessage& typedMessage = dynamic_cast<const TMessage&>(message); + (observer.*function_) (typedMessage); + } } virtual const MessageIdentifier& GetMessageIdentifier() @@ -109,41 +93,9 @@ return TMessage::GetStaticIdentifier(); } - virtual IObserver* GetObserver() const + virtual boost::weak_ptr<IObserver> GetObserver() const { - return &observer_; + return observer_; } }; - -#if 0 /* __cplusplus >= 201103L*/ - -#include <functional> - - template <typename TMessage> - class LambdaCallable : public MessageHandler<TMessage> - { - private: - - IObserver& observer_; - std::function<void (const TMessage&)> lambda_; - - public: - LambdaCallable(IObserver& observer, - std::function<void (const TMessage&)> lambdaFunction) : - observer_(observer), - lambda_(lambdaFunction) - { - } - - virtual void Apply(const IMessage& message) - { - lambda_(dynamic_cast<const TMessage&>(message)); - } - - virtual IObserver* GetObserver() const - { - return &observer_; - } - }; -#endif //__cplusplus >= 201103L }
--- a/Framework/Messages/IMessage.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Messages/IMessage.h Fri Nov 29 11:03:41 2019 +0100 @@ -21,6 +21,7 @@ #pragma once +#include <boost/lexical_cast.hpp> #include <boost/noncopyable.hpp> #include <string.h> @@ -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<std::string>(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); + } };
--- a/Framework/Messages/IMessageEmitter.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Messages/IMessageEmitter.h Fri Nov 29 11:03:41 2019 +0100 @@ -24,6 +24,8 @@ #include "IObserver.h" #include "IMessage.h" +#include <boost/weak_ptr.hpp> + namespace OrthancStone { /** @@ -39,7 +41,7 @@ { } - virtual void EmitMessage(const IObserver& observer, + virtual void EmitMessage(boost::weak_ptr<IObserver> observer, const IMessage& message) = 0; }; }
--- a/Framework/Messages/IObservable.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Messages/IObservable.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -21,8 +21,9 @@ #include "IObservable.h" +#include "../StoneException.h" + #include <Core/Logging.h> -#include <Core/OrthancException.h> #include <cassert> @@ -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<IObserver&>(*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<ICallable*>::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<IObserver> 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<IObserver> 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<IObserver> lock(observer.lock()); + if (lock) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + EmitMessageInternal(lock.get(), message); } - - forwarders_.insert(forwarder); } }
--- a/Framework/Messages/IObservable.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Messages/IObservable.h Fri Nov 29 11:03:41 2019 +0100 @@ -24,8 +24,6 @@ #include "../StoneEnumerations.h" #include "ICallable.h" #include "IObserver.h" -#include "MessageBroker.h" -#include "MessageForwarder.h" #include <set> #include <map> @@ -37,39 +35,20 @@ private: typedef std::map<MessageIdentifier, std::set<ICallable*> > Callables; - typedef std::set<IMessageForwarder*> 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 ownsership of the callable + void RegisterCallable(ICallable* callable); void BroadcastMessage(const IMessage& message); - void EmitMessage(const IObserver& observer, + void EmitMessage(boost::weak_ptr<IObserver> observer, const IMessage& message); - - // Takes ownsership - void RegisterForwarder(IMessageForwarder* forwarder); }; }
--- a/Framework/Messages/IObserver.cpp Thu Nov 28 18:28:15 2019 +0100 +++ /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-2019 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You 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 "IObserver.h" - -#include "IMessage.h" -#include "../StoneException.h" - -#include <Core/Logging.h> -#include <Core/Toolbox.h> - -namespace OrthancStone -{ - IObserver::IObserver(MessageBroker& broker) - : broker_(broker) - , fingerprint_() - { - // we store the fingerprint_ as a char array to avoid problems when - // reading it in a deceased object. - // remember this is panic-level code to track zombie object usage - std::string fingerprint = Orthanc::Toolbox::GenerateUuid(); - const char* fingerprintRaw = fingerprint.c_str(); - memcpy(fingerprint_, fingerprintRaw, 37); - broker_.Register(*this); - } - - - IObserver::~IObserver() - { - try - { - LOG(TRACE) << "IObserver(" << std::hex << this << std::dec << ")::~IObserver : fingerprint_ == " << fingerprint_; - const char* deadMarker = "deadbeef-dead-dead-0000-0000deadbeef"; - ORTHANC_ASSERT(strlen(deadMarker) == 36); - memcpy(fingerprint_, deadMarker, 37); - 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"; - } - } - - - bool IObserver::DoesFingerprintLookGood() const - { - for (size_t i = 0; i < 36; ++i) { - bool ok = false; - if (fingerprint_[i] >= 'a' && fingerprint_[i] <= 'f') - ok = true; - if (fingerprint_[i] >= '0' && fingerprint_[i] <= '9') - ok = true; - if (fingerprint_[i] == '-') - ok = true; - if (!ok) - return false; - } - return fingerprint_[36] == 0; - } -}
--- a/Framework/Messages/IObserver.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Messages/IObserver.h Fri Nov 29 11:03:41 2019 +0100 @@ -21,33 +21,19 @@ #pragma once -#include "MessageBroker.h" +#include <boost/noncopyable.hpp> namespace OrthancStone { class IObserver : public boost::noncopyable { - private: - MessageBroker& broker_; - // the following is a UUID that is used to disambiguate different observers - // that may have the same address - char fingerprint_[37]; - public: - IObserver(MessageBroker& broker); - - virtual ~IObserver(); - - const char* GetFingerprint() const + IObserver() { - return fingerprint_; } - bool DoesFingerprintLookGood() const; - - MessageBroker& GetBroker() const + virtual ~IObserver() { - return broker_; } }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/LockingEmitter.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,43 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "LockingEmitter.h" + +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + namespace Deprecated + { + void LockingEmitter::EmitMessage(boost::weak_ptr<IObserver> observer, + const IMessage& message) + { + 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(); + } + } + } +}
--- a/Framework/Messages/LockingEmitter.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Messages/LockingEmitter.h Fri Nov 29 11:03:41 2019 +0100 @@ -26,89 +26,67 @@ #include "IMessageEmitter.h" #include "IObservable.h" -#include <boost/thread.hpp> +#include <Core/Enumerations.h> // For ORTHANC_OVERRIDE + +#include <boost/thread/shared_mutex.hpp> namespace OrthancStone { - /** - * This class is used when using the ThreadedOracle : since messages - * can be sent from multiple Oracle threads, this IMessageEmitter - * implementation serializes the callbacks. - * - * The internal mutex used in Oracle messaging can also be used to - * protect the application data. Thus, this class can be used as a single - * application-wide mutex. - */ - class LockingEmitter : public IMessageEmitter + namespace Deprecated { - private: - boost::shared_mutex mutex_; - MessageBroker broker_; - IObservable oracleObservable_; - - public: - LockingEmitter() : - oracleObservable_(broker_) + /** + * 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 { - } - - MessageBroker& GetBroker() - { - return broker_; - } + private: + boost::shared_mutex mutex_; + IObservable oracleObservable_; - 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(); - } - } + public: + virtual void EmitMessage(boost::weak_ptr<IObserver> observer, + const IMessage& message) ORTHANC_OVERRIDE; - class ReaderLock : public boost::noncopyable - { - private: - LockingEmitter& that_; - boost::shared_lock<boost::shared_mutex> lock_; + class ReaderLock : public boost::noncopyable + { + private: + LockingEmitter& that_; + boost::shared_lock<boost::shared_mutex> lock_; - public: - ReaderLock(LockingEmitter& that) : + public: + ReaderLock(LockingEmitter& that) : that_(that), lock_(that.mutex_) - { - } - }; + { + } + }; - class WriterLock : public boost::noncopyable - { - private: - LockingEmitter& that_; - boost::unique_lock<boost::shared_mutex> lock_; + class WriterLock : public boost::noncopyable + { + private: + LockingEmitter& that_; + boost::unique_lock<boost::shared_mutex> lock_; - public: - WriterLock(LockingEmitter& that) : + public: + WriterLock(LockingEmitter& that) : that_(that), lock_(that.mutex_) - { - } - - MessageBroker& GetBroker() - { - return that_.broker_; - } + { + } - IObservable& GetOracleObservable() - { - return that_.oracleObservable_; - } + IObservable& GetOracleObservable() + { + return that_.oracleObservable_; + } + }; }; - }; + } }
--- a/Framework/Messages/MessageBroker.h Thu Nov 28 18:28:15 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - -#pragma once - -#include "boost/noncopyable.hpp" - -#include <set> - -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<const IObserver*> activeObservers_; // the list of observers that are currently alive (that have not been deleted) - - public: - MessageBroker() - { - } - - void Register(const IObserver& observer) - { - activeObservers_.insert(&observer); - } - - void Unregister(const IObserver& observer) - { - activeObservers_.erase(&observer); - } - - bool IsActive(const IObserver& observer) - { - return activeObservers_.find(&observer) != activeObservers_.end(); - } - }; -}
--- a/Framework/Messages/MessageForwarder.cpp Thu Nov 28 18:28:15 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "MessageForwarder.h" - -#include "IObservable.h" - -namespace OrthancStone -{ - - void IMessageForwarder::ForwardMessageInternal(const IMessage& message) - { - emitter_.BroadcastMessage(message); - } - - void IMessageForwarder::RegisterForwarderInEmitter() - { - emitter_.RegisterForwarder(this); - } -}
--- a/Framework/Messages/MessageForwarder.h Thu Nov 28 18:28:15 2019 +0100 +++ /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-2019 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ICallable.h" -#include "IObserver.h" - -#include <boost/noncopyable.hpp> - -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<A::MessageType>(broker, *this) // where "this" is B - * - * in C: - * B.RegisterObserverCallback(new Callable<C, A:MessageTyper>(*this, &B::MyCallback)) // where "this" is C - */ - template<typename TMessage> - class MessageForwarder : public IMessageForwarder, public Callable<MessageForwarder<TMessage>, TMessage> - { - public: - MessageForwarder(MessageBroker& broker, - IObservable& emitter // the object that will emit the messages to forward - ) - : IMessageForwarder(broker, emitter), - Callable<MessageForwarder<TMessage>, TMessage>(*this, &MessageForwarder::ForwardMessage) - { - RegisterForwarderInEmitter(); - } - -protected: - void ForwardMessage(const TMessage& message) - { - ForwardMessageInternal(message); - } - - }; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Messages/ObserverBase.h Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,68 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ICallable.h" +#include "IObserver.h" +#include "IObservable.h" + +#include <Core/OrthancException.h> + +#include <boost/enable_shared_from_this.hpp> + +namespace OrthancStone +{ + template <typename TObserver> + class ObserverBase : + public IObserver, + public boost::enable_shared_from_this<TObserver> + { + public: + boost::shared_ptr<TObserver> 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 <typename TMessage> + ICallable* CreateCallable(void (TObserver::* MemberMethod) (const TMessage&)) + { + return new Callable<TObserver, TMessage>(GetSharedObserver(), MemberMethod); + } + + template <typename TMessage> + void Register(IObservable& observable, + void (TObserver::* MemberMethod) (const TMessage&)) + { + observable.RegisterCallable(CreateCallable(MemberMethod)); + } + }; +}
--- a/Framework/OpenGL/OpenGLIncludes.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/OpenGL/OpenGLIncludes.h Fri Nov 29 11:03:41 2019 +0100 @@ -32,6 +32,8 @@ #if defined(__APPLE__) # include <OpenGL/gl.h> # include <OpenGL/glext.h> +#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 <GL/glew.h>
--- a/Framework/OpenGL/SdlOpenGLContext.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/OpenGL/SdlOpenGLContext.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -68,6 +68,7 @@ GLenum err = glewInit(); if (GLEW_OK != err) { + LOG(ERROR) << glewGetErrorString(err); throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Cannot initialize glew"); }
--- a/Framework/OpenGL/SdlOpenGLContext.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/OpenGL/SdlOpenGLContext.h Fri Nov 29 11:03:41 2019 +0100 @@ -26,6 +26,8 @@ #include "IOpenGLContext.h" #include "../Viewport/SdlWindow.h" +#include <SDL_render.h> + #include <Core/Enumerations.h> namespace OrthancStone
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GenericOracleRunner.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,518 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You 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 "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 <dcmtk/dcmdata/dcdeftag.h> +# include <dcmtk/dcmdata/dcfilefo.h> +static unsigned int BUCKET_DICOMDIR = 0; +static unsigned int BUCKET_SOP = 1; +#endif + +#include <Core/Compression/GzipCompressor.h> +#include <Core/HttpClient.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> +#include <Core/SystemToolbox.h> + +#include <boost/filesystem.hpp> + + + +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<IObserver> 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.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<IObserver> 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<IObserver> receiver, + IMessageEmitter& emitter, + const Orthanc::WebServiceParameters& orthanc, + 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(receiver, emitter, answer, answerHeaders); + } + + + static void RunInternal(boost::weak_ptr<IObserver> receiver, + IMessageEmitter& emitter, + const Orthanc::WebServiceParameters& orthanc, + 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(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<IObserver> 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<uint64_t>(static_cast<size_t>(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::auto_ptr<Orthanc::ParsedDicomFile> 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<IObserver> receiver, + IMessageEmitter& emitter, + boost::shared_ptr<ParsedDicomCache> 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::auto_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicom(fileSize, path, command.IsPixelDataIncluded())); + + if (fileSize != static_cast<size_t>(fileSize)) + { + // Cannot load such a large file on 32-bit architecture + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); + } + + { + ParseDicomSuccessMessage message + (command, *parsed, static_cast<size_t>(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<size_t>(fileSize), command.IsPixelDataIncluded()); + } + } + + + static void RunInternal(boost::weak_ptr<IObserver> receiver, + IMessageEmitter& emitter, + boost::shared_ptr<ParsedDicomCache> 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<const HttpCommand&>(command.GetRestCommand())); + break; + + case IOracleCommand::Type_OrthancRestApi: + RunOrthancRestApiCommand(answer, answerHeaders, orthanc, + dynamic_cast<const OrthancRestApiCommand&>(command.GetRestCommand())); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + size_t fileSize; + std::auto_ptr<Orthanc::ParsedDicomFile> 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<IObserver> 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<const HttpCommand&>(command)); + break; + + case IOracleCommand::Type_OrthancRestApi: + RunInternal(receiver, emitter, orthanc_, + dynamic_cast<const OrthancRestApiCommand&>(command)); + break; + + case IOracleCommand::Type_GetOrthancImage: + RunInternal(receiver, emitter, orthanc_, + dynamic_cast<const GetOrthancImageCommand&>(command)); + break; + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + RunInternal(receiver, emitter, orthanc_, + dynamic_cast<const GetOrthancWebViewerJpegCommand&>(command)); + break; + + case IOracleCommand::Type_ReadFile: + RunInternal(receiver, emitter, rootDirectory_, + dynamic_cast<const ReadFileCommand&>(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<const ParseDicomFromFileCommand&>(command)); + break; + + case IOracleCommand::Type_ParseDicomFromWado: + RunInternal(receiver, emitter, dicomCache_, orthanc_, + dynamic_cast<const ParseDicomFromWadoCommand&>(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); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GenericOracleRunner.h Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,87 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_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 <Core/Enumerations.h> // For ORTHANC_OVERRIDE +#include <Core/WebServiceParameters.h> + +namespace OrthancStone +{ + class GenericOracleRunner : public boost::noncopyable + { + private: + Orthanc::WebServiceParameters orthanc_; + std::string rootDirectory_; + +#if ORTHANC_ENABLE_DCMTK == 1 + boost::shared_ptr<ParsedDicomCache> 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<ParsedDicomCache> cache) + { + dicomCache_ = cache; + } +#endif + + void Run(boost::weak_ptr<IObserver> receiver, + IMessageEmitter& emitter, + const IOracleCommand& command); + }; +}
--- a/Framework/Oracle/GetOrthancImageCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/GetOrthancImageCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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,32 +44,43 @@ } - 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<std::string>(frame) + "/" + GetFormatSuffix(pixelFormat)); + } + + + void GetOrthancImageCommand::ProcessHttpAnswer(boost::weak_ptr<IObserver> receiver, + IMessageEmitter& emitter, const std::string& answer, const HttpHeaders& answerHeaders) const { @@ -147,7 +144,7 @@ } } - SuccessMessage message(*this, image.release(), contentType); + SuccessMessage message(*this, *image, contentType); emitter.EmitMessage(receiver, message); } }
--- a/Framework/Oracle/GetOrthancImageCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/GetOrthancImageCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -22,7 +22,7 @@ #pragma once #include "../Messages/IMessageEmitter.h" -#include "OracleCommandWithPayload.h" +#include "OracleCommandBase.h" #include <Core/Images/ImageAccessor.h> @@ -30,7 +30,7 @@ namespace OrthancStone { - class GetOrthancImageCommand : public OracleCommandWithPayload + class GetOrthancImageCommand : public OracleCommandBase { public: typedef std::map<std::string, std::string> HttpHeaders; @@ -40,17 +40,22 @@ ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); private: - std::auto_ptr<Orthanc::ImageAccessor> 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<IObserver> receiver, + IMessageEmitter& emitter, const std::string& answer, const HttpHeaders& answerHeaders) const; };
--- a/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -34,18 +34,6 @@ 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 +64,8 @@ } - void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(IMessageEmitter& emitter, - const IObserver& receiver, + void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(boost::weak_ptr<IObserver> receiver, + IMessageEmitter& emitter, const std::string& answer) const { // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()" @@ -149,7 +137,7 @@ } else { - SuccessMessage message(*this, reader.release()); + SuccessMessage message(*this, *reader); emitter.EmitMessage(receiver, message); return; } @@ -168,7 +156,7 @@ } else { - SuccessMessage message(*this, reader.release()); + SuccessMessage message(*this, *reader); emitter.EmitMessage(receiver, message); return; } @@ -210,8 +198,8 @@ float offset = static_cast<float>(stretchLow) / scaling; Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); } - - SuccessMessage message(*this, image.release()); + + SuccessMessage message(*this, *image); emitter.EmitMessage(receiver, message); } }
--- a/Framework/Oracle/GetOrthancWebViewerJpegCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -22,7 +22,7 @@ #pragma once #include "../Messages/IMessageEmitter.h" -#include "OracleCommandWithPayload.h" +#include "OracleCommandBase.h" #include <Core/Images/ImageAccessor.h> @@ -30,7 +30,7 @@ namespace OrthancStone { - class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload + class GetOrthancWebViewerJpegCommand : public OracleCommandBase { public: typedef std::map<std::string, std::string> HttpHeaders; @@ -40,15 +40,19 @@ ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); private: - std::auto_ptr<Orthanc::ImageAccessor> 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<IObserver> receiver, + IMessageEmitter& emitter, const std::string& answer) const; }; }
--- a/Framework/Oracle/HttpCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/HttpCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -28,16 +28,6 @@ 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 +66,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); + } + } }
--- a/Framework/Oracle/HttpCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/HttpCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -22,7 +22,7 @@ #pragma once #include "../Messages/IMessage.h" -#include "OracleCommandWithPayload.h" +#include "OracleCommandBase.h" #include <Core/Enumerations.h> @@ -31,7 +31,7 @@ namespace OrthancStone { - class HttpCommand : public OracleCommandWithPayload + class HttpCommand : public OracleCommandBase { public: typedef std::map<std::string, std::string> 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; }; }
--- a/Framework/Oracle/IOracle.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/IOracle.h Fri Nov 29 11:03:41 2019 +0100 @@ -24,6 +24,8 @@ #include "../Messages/IObserver.h" #include "IOracleCommand.h" +#include <boost/shared_ptr.hpp> + 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<IObserver> receiver, IOracleCommand* command) = 0; // Takes ownership }; }
--- a/Framework/Oracle/IOracleCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/IOracleCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -21,7 +21,7 @@ #pragma once -#include <boost/noncopyable.hpp> +#include <Core/IDynamicObject.h> 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; }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OracleCommandBase.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,67 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "OracleCommandBase.h" + +#include <Core/OrthancException.h> + +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); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OracleCommandBase.h Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,47 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IOracleCommand.h" + +#include <memory> + +namespace OrthancStone +{ + class OracleCommandBase : public IOracleCommand + { + private: + std::auto_ptr<Orthanc::IDynamicObject> payload_; + + public: + void AcquirePayload(Orthanc::IDynamicObject* payload); + + virtual bool HasPayload() const + { + return (payload_.get() != NULL); + } + + virtual Orthanc::IDynamicObject& GetPayload() const; + + Orthanc::IDynamicObject* ReleasePayload(); + }; +}
--- a/Framework/Oracle/OracleCommandExceptionMessage.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/OracleCommandExceptionMessage.h Fri Nov 29 11:03:41 2019 +0100 @@ -28,34 +28,28 @@ namespace OrthancStone { - class OracleCommandExceptionMessage : public IMessage + class OracleCommandExceptionMessage : public OriginMessage<IOracleCommand> { 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_;
--- a/Framework/Oracle/OracleCommandWithPayload.cpp Thu Nov 28 18:28:15 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "OracleCommandWithPayload.h" - -#include <Core/OrthancException.h> - -namespace OrthancStone -{ - void OracleCommandWithPayload::SetPayload(Orthanc::IDynamicObject* payload) - { - if (payload == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - payload_.reset(payload); - } - } - - - Orthanc::IDynamicObject& OracleCommandWithPayload::GetPayload() const - { - if (HasPayload()) - { - return *payload_; - } - else - { - 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); - } - } -}
--- a/Framework/Oracle/OracleCommandWithPayload.h Thu Nov 28 18:28:15 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2019 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IOracleCommand.h" - -#include <Core/IDynamicObject.h> - -#include <memory> - -namespace OrthancStone -{ - class OracleCommandWithPayload : public IOracleCommand - { - private: - std::auto_ptr<Orthanc::IDynamicObject> payload_; - - public: - void SetPayload(Orthanc::IDynamicObject* payload); - - bool HasPayload() const - { - return (payload_.get() != NULL); - } - - Orthanc::IDynamicObject& GetPayload() const; - - Orthanc::IDynamicObject* ReleasePayload(); - }; -}
--- a/Framework/Oracle/OrthancRestApiCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/OrthancRestApiCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -28,16 +28,6 @@ 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 +41,8 @@ OrthancRestApiCommand::OrthancRestApiCommand() : method_(Orthanc::HttpMethod_Get), uri_("/"), - timeout_(600) + timeout_(600), + applyPlugins_(false) { }
--- a/Framework/Oracle/OrthancRestApiCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/OrthancRestApiCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -22,7 +22,7 @@ #pragma once #include "../Messages/IMessage.h" -#include "OracleCommandWithPayload.h" +#include "OracleCommandBase.h" #include <Core/Enumerations.h> @@ -31,7 +31,7 @@ namespace OrthancStone { - class OrthancRestApiCommand : public OracleCommandWithPayload + class OrthancRestApiCommand : public OracleCommandBase { public: typedef std::map<std::string, std::string> 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_; + } }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromFileCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,43 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "ParseDicomFromFileCommand.h" + +#include <Core/OrthancException.h> + +#include <boost/filesystem/path.hpp> + +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(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromFileCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,84 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "OracleCommandBase.h" + +#include <string> + +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; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromWadoCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,58 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "ParseDicomFromWadoCommand.h" + +#include <Core/OrthancException.h> + +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_; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromWadoCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,54 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "OracleCommandBase.h" + +#include <string> + +namespace OrthancStone +{ + class ParseDicomFromWadoCommand : public OracleCommandBase + { + private: + std::string sopInstanceUid_; + std::auto_ptr<IOracleCommand> 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; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomSuccessMessage.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,107 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You 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 "ParseDicomSuccessMessage.h" + +#include <Core/DicomParsing/ParsedDicomFile.h> +#include <Core/HttpServer/MultipartStreamReader.h> +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + class MultipartHandler : public Orthanc::MultipartStreamReader::IHandler + { + private: + std::auto_ptr<Orthanc::ParsedDicomFile> dicom_; + size_t size_; + + public: + MultipartHandler() : + size_(0) + { + } + + virtual void HandlePart(const std::map<std::string, std::string>& 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<std::string, std::string>& 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"); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomSuccessMessage.h Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,85 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_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 <map> + +namespace Orthanc +{ + class ParsedDicomFile; +} + +namespace OrthancStone +{ + class ParseDicomSuccessMessage : public OriginMessage<OracleCommandBase> + { + 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<std::string, std::string>& headers); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ReadFileCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,78 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Messages/IMessage.h" +#include "OracleCommandBase.h" + +namespace OrthancStone +{ + class ReadFileCommand : public OracleCommandBase + { + public: + class SuccessMessage : public OriginMessage<ReadFileCommand> + { + 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_; + } + }; +}
--- a/Framework/Oracle/SleepOracleCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/SleepOracleCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -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_;
--- a/Framework/Oracle/ThreadedOracle.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/ThreadedOracle.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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 <Core/Compression/GzipCompressor.h> -#include <Core/HttpClient.h> +#include <Core/Logging.h> #include <Core/OrthancException.h> -#include <Core/Toolbox.h> - namespace OrthancStone { class ThreadedOracle::Item : public Orthanc::IDynamicObject { private: - const IObserver& receiver_; + boost::weak_ptr<IObserver> receiver_; std::auto_ptr<IOracleCommand> command_; public: - Item(const IObserver& receiver, + Item(boost::weak_ptr<IObserver> receiver, IOracleCommand* command) : receiver_(receiver), command_(command) @@ -54,7 +46,7 @@ } } - const IObserver& GetReceiver() const + boost::weak_ptr<IObserver> GetReceiver() { return receiver_; } @@ -73,12 +65,12 @@ class Item { private: - const IObserver& receiver_; + boost::weak_ptr<IObserver> receiver_; std::auto_ptr<SleepOracleCommand> command_; boost::posix_time::ptime expiration_; public: - Item(const IObserver& receiver, + Item(boost::weak_ptr<IObserver> receiver, SleepOracleCommand* command) : receiver_(receiver), command_(command) @@ -123,7 +115,7 @@ } } - void Add(const IObserver& receiver, + void Add(boost::weak_ptr<IObserver> 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::auto_ptr<Orthanc::IDynamicObject> object(queue_.Dequeue(100)); @@ -316,60 +160,37 @@ { Item& item = dynamic_cast<Item&>(*object); - try + if (item.GetCommand().GetType() == IOracleCommand::Type_Sleep) { - switch (item.GetCommand().GetType()) + SleepOracleCommand& command = dynamic_cast<SleepOracleCommand&>(item.GetCommand()); + + std::auto_ptr<SleepOracleCommand> copy(new SleepOracleCommand(command.GetDelay())); + + if (command.HasPayload()) { - case IOracleCommand::Type_Sleep: - { - SleepOracleCommand& command = dynamic_cast<SleepOracleCommand&>(item.GetCommand()); - - std::auto_ptr<SleepOracleCommand> copy(new SleepOracleCommand(command.GetDelay())); - - if (command.HasPayload()) - { - copy->SetPayload(command.ReleasePayload()); - } - - sleepingCommands_->Add(item.GetReceiver(), copy.release()); - - break; - } - - case IOracleCommand::Type_Http: - Execute(emitter_, item.GetReceiver(), - dynamic_cast<const HttpCommand&>(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<const OrthancRestApiCommand&>(item.GetCommand())); - break; - - case IOracleCommand::Type_GetOrthancImage: - Execute(emitter_, orthanc_, item.GetReceiver(), - dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand())); - break; - - case IOracleCommand::Type_GetOrthancWebViewerJpeg: - Execute(emitter_, orthanc_, item.GetReceiver(), - dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand())); - break; + { + 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<IObserver> receiver, IOracleCommand* command) { - queue_.Enqueue(new Item(receiver, command)); + std::auto_ptr<Item> 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; + } + } } }
--- a/Framework/Oracle/ThreadedOracle.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/ThreadedOracle.h Fri Nov 29 11:03:41 2019 +0100 @@ -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 <Core/WebServiceParameters.h> #include <Core/MultiThreading/SharedMessageQueue.h> @@ -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<ParsedDicomCache> 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<IObserver> receiver, + IOracleCommand* command) ORTHANC_OVERRIDE; }; }
--- a/Framework/Oracle/WebAssemblyOracle.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/WebAssemblyOracle.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -695,7 +695,7 @@ - void WebAssemblyOracle::Schedule(const IObserver& receiver, + void WebAssemblyOracle::Schedule(boost::shared_ptr<IObserver>& receiver, IOracleCommand* command) { LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = "
--- a/Framework/Oracle/WebAssemblyOracle.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Oracle/WebAssemblyOracle.h Fri Nov 29 11:03:41 2019 +0100 @@ -76,7 +76,7 @@ orthancRoot_ = root; } - virtual void Schedule(const IObserver& receiver, - IOracleCommand* command); + virtual void Schedule(boost::shared_ptr<IObserver>& receiver, + IOracleCommand* command) ORTHANC_OVERRIDE; }; }
--- a/Framework/Radiography/RadiographyAlphaLayer.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyAlphaLayer.h Fri Nov 29 11:03:41 2019 +0100 @@ -37,8 +37,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) { }
--- a/Framework/Radiography/RadiographyDicomLayer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyDicomLayer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -48,7 +48,8 @@ } - RadiographyDicomLayer::RadiographyDicomLayer(MessageBroker& broker, const RadiographyScene& scene) : RadiographyLayer(broker, scene) + RadiographyDicomLayer::RadiographyDicomLayer(const RadiographyScene& scene) : + RadiographyLayer(scene) { }
--- a/Framework/Radiography/RadiographyDicomLayer.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyDicomLayer.h Fri Nov 29 11:03:41 2019 +0100 @@ -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) {
--- a/Framework/Radiography/RadiographyLayer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyLayer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -119,8 +119,7 @@ } - RadiographyLayer::RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene) : - IObservable(broker), + RadiographyLayer::RadiographyLayer(const RadiographyScene& scene) : index_(0), hasSize_(false), width_(0),
--- a/Framework/Radiography/RadiographyLayer.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyLayer.h Fri Nov 29 11:03:41 2019 +0100 @@ -248,7 +248,7 @@ double zoom); public: - RadiographyLayer(MessageBroker& broker, const RadiographyScene& scene); + RadiographyLayer(const RadiographyScene& scene); virtual ~RadiographyLayer() {
--- a/Framework/Radiography/RadiographyMaskLayer.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyMaskLayer.h Fri Nov 29 11:03:41 2019 +0100 @@ -40,9 +40,9 @@ mutable std::auto_ptr<Orthanc::ImageAccessor> 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)
--- a/Framework/Radiography/RadiographyScene.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyScene.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -134,15 +134,13 @@ std::auto_ptr<RadiographyLayer> raii(layer); - // LOG(INFO) << "Registering layer: " << countLayers_; - size_t index = nextLayerIndex_++; raii->SetIndex(index); layers_[index] = raii.release(); BroadcastMessage(GeometryChangedMessage(*this, *layer)); BroadcastMessage(ContentChangedMessage(*this, *layer)); - layer->RegisterObserverCallback(new Callable<RadiographyScene, RadiographyLayer::LayerEditedMessage>(*this, &RadiographyScene::OnLayerEdited)); + Register<RadiographyLayer::LayerEditedMessage>(*layer, &RadiographyScene::OnLayerEdited); return *layer; } @@ -162,9 +160,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 @@ -286,7 +283,7 @@ uint8_t foreground, RadiographyLayer::Geometry* geometry) { - std::auto_ptr<RadiographyTextLayer> alpha(new RadiographyTextLayer(IObservable::GetBroker(), *this)); + std::auto_ptr<RadiographyTextLayer> alpha(new RadiographyTextLayer(*this)); alpha->LoadText(utf8, fontSize, foreground); if (geometry != NULL) { @@ -333,7 +330,7 @@ float foreground, RadiographyLayer::Geometry* geometry) { - std::auto_ptr<RadiographyMaskLayer> mask(new RadiographyMaskLayer(IObservable::GetBroker(), *this, dicomLayer, foreground)); + std::auto_ptr<RadiographyMaskLayer> mask(new RadiographyMaskLayer(*this, dicomLayer, foreground)); mask->SetCorners(corners); if (geometry != NULL) { @@ -346,7 +343,7 @@ RadiographyLayer& RadiographyScene::LoadAlphaBitmap(Orthanc::ImageAccessor* bitmap, RadiographyLayer::Geometry *geometry) { - std::auto_ptr<RadiographyAlphaLayer> alpha(new RadiographyAlphaLayer(IObservable::GetBroker(), *this)); + std::auto_ptr<RadiographyAlphaLayer> alpha(new RadiographyAlphaLayer(*this)); alpha->SetAlpha(bitmap); if (geometry != NULL) { @@ -363,7 +360,7 @@ RadiographyPhotometricDisplayMode preferredPhotometricDisplayMode, RadiographyLayer::Geometry* geometry) { - RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this))); + RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(*this))); layer.SetInstance(instance, frame); @@ -385,7 +382,7 @@ bool httpCompression, RadiographyLayer::Geometry* geometry) { - RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this))); + RadiographyDicomLayer& layer = dynamic_cast<RadiographyDicomLayer&>(RegisterLayer(new RadiographyDicomLayer( *this))); layer.SetInstance(instance, frame); if (geometry != NULL) @@ -400,7 +397,7 @@ orthanc.GetBinaryAsync( uri, headers, new Callable<RadiographyScene, Deprecated::OrthancApiClient::BinaryResponseReadyMessage> - (*this, &RadiographyScene::OnTagsReceived), NULL, + (GetSharedObserver(), &RadiographyScene::OnTagsReceived), NULL, new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); } @@ -419,7 +416,7 @@ orthanc.GetBinaryAsync( uri, headers, new Callable<RadiographyScene, Deprecated::OrthancApiClient::BinaryResponseReadyMessage> - (*this, &RadiographyScene::OnFrameReceived), NULL, + (GetSharedObserver(), &RadiographyScene::OnFrameReceived), NULL, new Orthanc::SingleValueObject<size_t>(layer.GetIndex())); } @@ -429,7 +426,7 @@ RadiographyLayer& RadiographyScene::LoadDicomWebFrame(Deprecated::IWebService& web) { - RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(IObservable::GetBroker(), *this)); + RadiographyLayer& layer = RegisterLayer(new RadiographyDicomLayer(*this)); return layer; @@ -759,7 +756,7 @@ orthanc.PostJsonAsyncExpectJson( "/tools/create-dicom", createDicomRequestContent, new Callable<RadiographyScene, Deprecated::OrthancApiClient::JsonResponseReadyMessage> - (*this, &RadiographyScene::OnDicomExported), + (GetSharedObserver(), &RadiographyScene::OnDicomExported), NULL, NULL); }
--- a/Framework/Radiography/RadiographyScene.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyScene.h Fri Nov 29 11:03:41 2019 +0100 @@ -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" @@ -33,8 +34,8 @@ class RadiographyDicomLayer; class RadiographyScene : - public IObserver, - public IObservable + public ObserverBase<RadiographyScene>, + public IObservable { public: class GeometryChangedMessage : public OriginMessage<RadiographyScene> @@ -180,7 +181,7 @@ virtual void OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message); public: - RadiographyScene(MessageBroker& broker); + RadiographyScene(); virtual ~RadiographyScene();
--- a/Framework/Radiography/RadiographySceneReader.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographySceneReader.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -50,7 +50,7 @@ RadiographyDicomLayer* RadiographySceneReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry) { - return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, instanceId, frame, false, geometry))); + return dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(*orthancApiClient_, instanceId, frame, false, geometry))); } void RadiographySceneBuilder::Read(const Json::Value& input) @@ -151,7 +151,7 @@ if (jsonLayer["type"].asString() == "dicom") { ReadLayerGeometry(geometry, jsonLayer); - dicomLayer = dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry))); + dicomLayer = dynamic_cast<RadiographyDicomLayer*>(&(scene_.LoadDicomFrame(*orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry))); } else if (jsonLayer["type"].asString() == "mask") {
--- a/Framework/Radiography/RadiographySceneReader.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographySceneReader.h Fri Nov 29 11:03:41 2019 +0100 @@ -68,10 +68,12 @@ class RadiographySceneReader : public RadiographySceneBuilder { - Deprecated::OrthancApiClient& orthancApiClient_; + private: + boost::shared_ptr<Deprecated::OrthancApiClient> orthancApiClient_; public: - RadiographySceneReader(RadiographyScene& scene, Deprecated::OrthancApiClient& orthancApiClient) : + RadiographySceneReader(RadiographyScene& scene, + boost::shared_ptr<Deprecated::OrthancApiClient> orthancApiClient) : RadiographySceneBuilder(scene), orthancApiClient_(orthancApiClient) {
--- a/Framework/Radiography/RadiographyTextLayer.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyTextLayer.h Fri Nov 29 11:03:41 2019 +0100 @@ -37,8 +37,8 @@ static bool fontHasBeenConfigured_; static Orthanc::EmbeddedResources::FileResourceId fontResourceId_; public: - RadiographyTextLayer(MessageBroker& broker, const RadiographyScene& scene) : - RadiographyAlphaLayer(broker, scene) + RadiographyTextLayer(const RadiographyScene& scene) : + RadiographyAlphaLayer(scene) { }
--- a/Framework/Radiography/RadiographyWidget.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyWidget.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -164,11 +164,9 @@ } - RadiographyWidget::RadiographyWidget(MessageBroker& broker, - boost::shared_ptr<RadiographyScene> scene, + RadiographyWidget::RadiographyWidget(boost::shared_ptr<RadiographyScene> scene, const std::string& name) : WorldSceneWidget(name), - IObserver(broker), invert_(false), interpolation_(ImageInterpolation_Nearest), hasSelection_(false), @@ -249,24 +247,11 @@ void RadiographyWidget::SetScene(boost::shared_ptr<RadiographyScene> scene) { - if (scene_ != NULL) - { - scene_->Unregister(this); - } - scene_ = scene; - scene_->RegisterObserverCallback( - new Callable<RadiographyWidget, RadiographyScene::GeometryChangedMessage> - (*this, &RadiographyWidget::OnGeometryChanged)); - - scene_->RegisterObserverCallback( - new Callable<RadiographyWidget, RadiographyScene::ContentChangedMessage> - (*this, &RadiographyWidget::OnContentChanged)); - - scene_->RegisterObserverCallback( - new Callable<RadiographyWidget, RadiographyScene::LayerRemovedMessage> - (*this, &RadiographyWidget::OnLayerRemoved)); + Register<RadiographyScene::GeometryChangedMessage>(*scene_, &RadiographyWidget::OnGeometryChanged); + Register<RadiographyScene::ContentChangedMessage>(*scene_, &RadiographyWidget::OnContentChanged); + Register<RadiographyScene::LayerRemovedMessage>(*scene_, &RadiographyWidget::OnLayerRemoved); NotifyContentChanged();
--- a/Framework/Radiography/RadiographyWidget.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Radiography/RadiographyWidget.h Fri Nov 29 11:03:41 2019 +0100 @@ -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<RadiographyWidget> { private: boost::shared_ptr<RadiographyScene> scene_; @@ -60,8 +61,7 @@ bool IsInvertedInternal() const; public: - RadiographyWidget(MessageBroker& broker, - boost::shared_ptr<RadiographyScene> 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<RadiographyScene> 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
--- a/Framework/Scene2D/FloatTextureSceneLayer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/FloatTextureSceneLayer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -92,6 +92,14 @@ IncrementRevision(); } + + void FloatTextureSceneLayer::SetApplyLog(bool apply) + { + applyLog_ = apply; + IncrementRevision(); + } + + void FloatTextureSceneLayer::FitRange() { float minValue, maxValue; @@ -123,6 +131,7 @@ cloned->customCenter_ = customCenter_; cloned->customWidth_ = customWidth_; cloned->inverted_ = inverted_; + cloned->applyLog_ = applyLog_; return cloned.release(); }
--- a/Framework/Scene2D/FloatTextureSceneLayer.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/FloatTextureSceneLayer.h Fri Nov 29 11:03:41 2019 +0100 @@ -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
--- a/Framework/Scene2D/GrayscaleStyleConfigurator.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/GrayscaleStyleConfigurator.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -27,6 +27,17 @@ namespace OrthancStone { + GrayscaleStyleConfigurator::GrayscaleStyleConfigurator() : + revision_(0), + linearInterpolation_(false), + hasWindowing_(false), + customWindowWidth_(0), + customWindowCenter_(0), + inverted_(false), + applyLog_(false) + { + } + void GrayscaleStyleConfigurator::SetWindowing(ImageWindowing windowing) { hasWindowing_ = true; @@ -59,6 +70,12 @@ revision_++; } + void GrayscaleStyleConfigurator::SetApplyLog(bool apply) + { + applyLog_ = apply; + revision_++; + } + TextureBaseSceneLayer* GrayscaleStyleConfigurator::CreateTextureFromImage( const Orthanc::ImageAccessor& image) const { @@ -99,6 +116,8 @@ l.SetCustomWindowing(customWindowCenter_, customWindowWidth_); } } + l.SetInverted(inverted_); + l.SetApplyLog(applyLog_); } }
--- a/Framework/Scene2D/GrayscaleStyleConfigurator.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/GrayscaleStyleConfigurator.h Fri Nov 29 11:03:41 2019 +0100 @@ -39,17 +39,10 @@ float customWindowWidth_; float customWindowCenter_; bool inverted_; + bool applyLog_; public: - GrayscaleStyleConfigurator() : - revision_(0), - linearInterpolation_(false), - hasWindowing_(false), - customWindowWidth_(0), - customWindowCenter_(0), - inverted_(false) - { - } + GrayscaleStyleConfigurator(); void SetWindowing(ImageWindowing windowing); @@ -66,6 +59,13 @@ return linearInterpolation_; } + void SetApplyLog(bool apply); + + bool IsApplyLog() const + { + return applyLog_; + } + virtual uint64_t GetRevision() const { return revision_;
--- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<const float*>(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<float>(v)); + } + + assert(v >= 0.0f && v <= 255.0f); + uint8_t vv = static_cast<uint8_t>(v); if (l.IsInverted())
--- a/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<uint8_t>& lut = l.GetLookupTable(); - if (lut.size() != 4 * 256) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - assert(source.GetFormat() == Orthanc::PixelFormat_Float32 && - target.GetFormat() == Orthanc::PixelFormat_BGRA32 && - sizeof(float) == 4); - - for (unsigned int y = 0; y < height; y++) - { - const float* p = reinterpret_cast<const float*>(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); - - for (unsigned int x = 0; x < width; x++) - { - float v = (*p - a) * slope; - if (v <= 0) - { - v = 0; - } - else if (v >= 255) - { - v = 255; - } - - uint8_t vv = static_cast<uint8_t>(v); - - q[0] = lut[4 * vv + 2]; // B - q[1] = lut[4 * vv + 1]; // G - q[2] = lut[4 * vv + 0]; // R - q[3] = lut[4 * vv + 3]; // A - - p++; - q += 4; - } - } - + l.Render(target); + cairo_surface_mark_dirty(texture_.GetObject()); }
--- a/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -21,6 +21,8 @@ #include "OpenGLFloatTextureRenderer.h" +#include <Core/OrthancException.h> + 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()));
--- a/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -36,74 +36,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<uint8_t>& lut = layer.GetLookupTable(); - if (lut.size() != 4 * 256) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - assert(source.GetFormat() == Orthanc::PixelFormat_Float32 && - target.GetFormat() == Orthanc::PixelFormat_RGBA32 && - sizeof(float) == 4); - - for (unsigned int y = 0; y < height; y++) - { - const float* p = reinterpret_cast<const float*>(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); - - for (unsigned int x = 0; x < width; x++) - { - float v = (*p - a) * slope; - if (v <= 0) - { - v = 0; - } - else if (v >= 255) - { - v = 255; - } - - uint8_t vv = static_cast<uint8_t>(v); - - q[0] = lut[4 * vv + 0]; // R - q[1] = lut[4 * vv + 1]; // G - q[2] = lut[4 * vv + 2]; // B - q[3] = lut[4 * vv + 3]; // A - - p++; - q += 4; - } - } - } + layer.Render(*texture_); context_.MakeCurrent(); glTexture_.reset(new OpenGL::OpenGLTexture(context_));
--- a/Framework/Scene2D/LookupTableStyleConfigurator.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/LookupTableStyleConfigurator.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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_); } }
--- a/Framework/Scene2D/LookupTableStyleConfigurator.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/LookupTableStyleConfigurator.h Fri Nov 29 11:03:41 2019 +0100 @@ -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); }
--- a/Framework/Scene2D/LookupTableTextureSceneLayer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/LookupTableTextureSceneLayer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -132,6 +132,12 @@ } } + void LookupTableTextureSceneLayer::SetApplyLog(bool apply) + { + applyLog_ = apply; + IncrementRevision(); + } + void LookupTableTextureSceneLayer::FitRange() { Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue_, maxValue_, GetTexture()); @@ -158,4 +164,147 @@ return cloned.release(); } + + + // Templatized function to speed up computations, by avoiding + // testing conditions on each pixel + template <bool IsApplyLog, + Orthanc::PixelFormat TargetFormat> + static void RenderInternal(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + float minValue, + float slope, + const std::vector<uint8_t>& 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<const float*>(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + float v = (*p - 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<float>(v)); + } + + assert(v >= 0.0f && v <= 255.0f); + + uint8_t vv = static_cast<uint8_t>(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<uint8_t>& lut = GetLookupTable(); + if (lut.size() != 4 * 256) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + switch (target.GetFormat()) + { + case Orthanc::PixelFormat_RGBA32: + if (applyLog_) + { + RenderInternal<true, Orthanc::PixelFormat_RGBA32>(target, source, minValue, slope, lut); + } + else + { + RenderInternal<false, Orthanc::PixelFormat_RGBA32>(target, source, minValue, slope, lut); + } + break; + + case Orthanc::PixelFormat_BGRA32: + if (applyLog_) + { + RenderInternal<true, Orthanc::PixelFormat_BGRA32>(target, source, minValue, slope, lut); + } + else + { + RenderInternal<false, Orthanc::PixelFormat_BGRA32>(target, source, minValue, slope, lut); + } + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } }
--- a/Framework/Scene2D/LookupTableTextureSceneLayer.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/LookupTableTextureSceneLayer.h Fri Nov 29 11:03:41 2019 +0100 @@ -32,6 +32,7 @@ float minValue_; float maxValue_; std::vector<uint8_t> lut_; + bool applyLog_; void SetLookupTableRgb(const std::vector<uint8_t>& 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; }; }
--- a/Framework/Scene2D/ScenePoint2D.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2D/ScenePoint2D.h Fri Nov 29 11:03:41 2019 +0100 @@ -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); + } } }; }
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -42,8 +42,8 @@ // the params in the LayerHolder ctor specify the number of polyline and text // layers AngleMeasureTool::AngleMeasureTool( - MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW) - : MeasureTool(broker, controllerW) + boost::weak_ptr<ViewportController> controllerW) + : MeasureTool(controllerW) #if ORTHANC_STONE_ENABLE_OUTLINED_TEXT == 1 , layerHolder_(boost::make_shared<LayerHolder>(controllerW,1,5)) #else @@ -189,8 +189,9 @@ boost::weak_ptr<ViewportController> controllerW, const PointerEvent & e); */ + boost::shared_ptr<EditAngleMeasureTracker> editAngleMeasureTracker( - new EditAngleMeasureTracker(shared_from_this(), GetBroker(), GetController(), e)); + new EditAngleMeasureTracker(shared_from_this(), GetController(), e)); return editAngleMeasureTracker; }
--- a/Framework/Scene2DViewport/AngleMeasureTool.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/AngleMeasureTool.h Fri Nov 29 11:03:41 2019 +0100 @@ -37,10 +37,10 @@ namespace OrthancStone { - class AngleMeasureTool : public MeasureTool, public boost::enable_shared_from_this<AngleMeasureTool> + class AngleMeasureTool : public MeasureTool { public: - AngleMeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW); + AngleMeasureTool(boost::weak_ptr<ViewportController> controllerW); ~AngleMeasureTool();
--- a/Framework/Scene2DViewport/CreateAngleMeasureCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/CreateAngleMeasureCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -26,12 +26,11 @@ namespace OrthancStone { CreateAngleMeasureCommand::CreateAngleMeasureCommand( - MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW, ScenePoint2D point) : CreateMeasureCommand(controllerW) , measureTool_( - boost::make_shared<AngleMeasureTool>(boost::ref(broker), controllerW)) + boost::make_shared<AngleMeasureTool>(controllerW)) { GetController()->AddMeasureTool(measureTool_); measureTool_->SetSide1End(point);
--- a/Framework/Scene2DViewport/CreateAngleMeasureCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/CreateAngleMeasureCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -28,7 +28,6 @@ public: /** Ctor sets end of side 1*/ CreateAngleMeasureCommand( - MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW, ScenePoint2D point);
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -26,7 +26,6 @@ namespace OrthancStone { CreateAngleMeasureTracker::CreateAngleMeasureTracker( - MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e) : CreateMeasureTracker(controllerW) @@ -34,7 +33,6 @@ { command_.reset( new CreateAngleMeasureCommand( - broker, controllerW, e.GetMainPosition().Apply(GetScene().GetCanvasToSceneTransform()))); }
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.h Fri Nov 29 11:03:41 2019 +0100 @@ -38,7 +38,6 @@ must be supplied, too */ CreateAngleMeasureTracker( - MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e);
--- a/Framework/Scene2DViewport/CreateLineMeasureCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/CreateLineMeasureCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -26,12 +26,11 @@ namespace OrthancStone { CreateLineMeasureCommand::CreateLineMeasureCommand( - MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW, ScenePoint2D point) : CreateMeasureCommand(controllerW) , measureTool_( - boost::make_shared<LineMeasureTool>(boost::ref(broker), controllerW)) + boost::make_shared<LineMeasureTool>(controllerW)) { GetController()->AddMeasureTool(measureTool_); measureTool_->Set(point, point);
--- a/Framework/Scene2DViewport/CreateLineMeasureCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/CreateLineMeasureCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -27,7 +27,6 @@ { public: CreateLineMeasureCommand( - MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW, ScenePoint2D point);
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -26,14 +26,12 @@ namespace OrthancStone { CreateLineMeasureTracker::CreateLineMeasureTracker( - MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e) : CreateMeasureTracker(controllerW) { command_.reset( new CreateLineMeasureCommand( - broker, controllerW, e.GetMainPosition().Apply(GetScene().GetCanvasToSceneTransform()))); }
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.h Fri Nov 29 11:03:41 2019 +0100 @@ -38,7 +38,6 @@ must be supplied, too */ CreateLineMeasureTracker( - MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e);
--- a/Framework/Scene2DViewport/EditAngleMeasureCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/EditAngleMeasureCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -23,8 +23,7 @@ namespace OrthancStone { EditAngleMeasureCommand::EditAngleMeasureCommand( - boost::shared_ptr<AngleMeasureTool> measureTool, - MessageBroker& broker, + boost::shared_ptr<MeasureTool> measureTool, boost::weak_ptr<ViewportController> controllerW) : EditMeasureCommand(measureTool, controllerW) , measureTool_(measureTool) @@ -33,21 +32,21 @@ void EditAngleMeasureCommand::SetCenter(ScenePoint2D scenePos) { - measureTool_->SetCenter(scenePos); + dynamic_cast<AngleMeasureTool&>(*measureTool_).SetCenter(scenePos); mementoModified_ = measureTool_->GetMemento(); } void EditAngleMeasureCommand::SetSide1End(ScenePoint2D scenePos) { - measureTool_->SetSide1End(scenePos); + dynamic_cast<AngleMeasureTool&>(*measureTool_).SetSide1End(scenePos); mementoModified_ = measureTool_->GetMemento(); } void EditAngleMeasureCommand::SetSide2End(ScenePoint2D scenePos) { - measureTool_->SetSide2End(scenePos); + dynamic_cast<AngleMeasureTool&>(*measureTool_).SetSide2End(scenePos); mementoModified_ = measureTool_->GetMemento(); } }
--- a/Framework/Scene2DViewport/EditAngleMeasureCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/EditAngleMeasureCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -28,8 +28,7 @@ public: /** Ctor sets end of side 1*/ EditAngleMeasureCommand( - boost::shared_ptr<AngleMeasureTool> measureTool, - MessageBroker& broker, + boost::shared_ptr<MeasureTool> measureTool, boost::weak_ptr<ViewportController> controllerW); /** This method sets center*/ @@ -46,6 +45,6 @@ { return measureTool_; } - boost::shared_ptr<AngleMeasureTool> measureTool_; + boost::shared_ptr<MeasureTool> measureTool_; }; }
--- a/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -26,8 +26,7 @@ namespace OrthancStone { EditAngleMeasureTracker::EditAngleMeasureTracker( - boost::shared_ptr<AngleMeasureTool> measureTool, - MessageBroker& broker, + boost::shared_ptr<MeasureTool> measureTool, boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e) : EditMeasureTracker(controllerW, e) @@ -35,9 +34,9 @@ ScenePoint2D scenePos = e.GetMainPosition().Apply( GetScene().GetCanvasToSceneTransform()); - modifiedZone_ = measureTool->AngleHitTest(scenePos); + modifiedZone_ = dynamic_cast<AngleMeasureTool&>(*measureTool).AngleHitTest(scenePos); - command_.reset(new EditAngleMeasureCommand(measureTool, broker, controllerW)); + command_.reset(new EditAngleMeasureCommand(measureTool, controllerW)); } EditAngleMeasureTracker::~EditAngleMeasureTracker()
--- a/Framework/Scene2DViewport/EditAngleMeasureTracker.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.h Fri Nov 29 11:03:41 2019 +0100 @@ -37,8 +37,7 @@ must be supplied, too */ EditAngleMeasureTracker( - boost::shared_ptr<AngleMeasureTool> measureTool, - MessageBroker& broker, + boost::shared_ptr<MeasureTool> measureTool, boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e);
--- a/Framework/Scene2DViewport/EditLineMeasureCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/EditLineMeasureCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -23,8 +23,7 @@ namespace OrthancStone { EditLineMeasureCommand::EditLineMeasureCommand( - boost::shared_ptr<LineMeasureTool> measureTool, - MessageBroker& broker, + boost::shared_ptr<MeasureTool> measureTool, boost::weak_ptr<ViewportController> controllerW) : EditMeasureCommand(measureTool, controllerW) , measureTool_(measureTool) @@ -34,14 +33,14 @@ void EditLineMeasureCommand::SetStart(ScenePoint2D scenePos) { - measureTool_->SetStart(scenePos); + dynamic_cast<LineMeasureTool&>(*measureTool_).SetStart(scenePos); mementoModified_ = measureTool_->GetMemento(); } void EditLineMeasureCommand::SetEnd(ScenePoint2D scenePos) { - measureTool_->SetEnd(scenePos); + dynamic_cast<LineMeasureTool&>(*measureTool_).SetEnd(scenePos); mementoModified_ = measureTool_->GetMemento(); } }
--- a/Framework/Scene2DViewport/EditLineMeasureCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/EditLineMeasureCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -27,8 +27,7 @@ { public: EditLineMeasureCommand( - boost::shared_ptr<LineMeasureTool> measureTool, - MessageBroker& broker, + boost::shared_ptr<MeasureTool> measureTool, boost::weak_ptr<ViewportController> controllerW); void SetStart(ScenePoint2D scenePos); @@ -39,7 +38,6 @@ { return measureTool_; } - boost::shared_ptr<LineMeasureTool> measureTool_; + boost::shared_ptr<MeasureTool> measureTool_; }; } -
--- a/Framework/Scene2DViewport/EditLineMeasureTracker.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/EditLineMeasureTracker.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -27,8 +27,7 @@ namespace OrthancStone { EditLineMeasureTracker::EditLineMeasureTracker( - boost::shared_ptr<LineMeasureTool> measureTool, - MessageBroker& broker, + boost::shared_ptr<MeasureTool> measureTool, boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e) : EditMeasureTracker(controllerW, e) @@ -36,13 +35,9 @@ ScenePoint2D scenePos = e.GetMainPosition().Apply( GetScene().GetCanvasToSceneTransform()); - modifiedZone_ = measureTool->LineHitTest(scenePos); + modifiedZone_ = dynamic_cast<LineMeasureTool&>(*measureTool).LineHitTest(scenePos); - command_.reset( - new EditLineMeasureCommand( - measureTool, - broker, - controllerW)); + command_.reset(new EditLineMeasureCommand(measureTool, controllerW)); } EditLineMeasureTracker::~EditLineMeasureTracker()
--- a/Framework/Scene2DViewport/EditLineMeasureTracker.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/EditLineMeasureTracker.h Fri Nov 29 11:03:41 2019 +0100 @@ -37,8 +37,7 @@ must be supplied, too */ EditLineMeasureTracker( - boost::shared_ptr<LineMeasureTool> measureTool, - MessageBroker& broker, + boost::shared_ptr<MeasureTool> measureTool, boost::weak_ptr<ViewportController> controllerW, const PointerEvent& e);
--- a/Framework/Scene2DViewport/LineMeasureTool.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/LineMeasureTool.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -32,8 +32,8 @@ { LineMeasureTool::LineMeasureTool( - MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW) - : MeasureTool(broker, controllerW) + boost::weak_ptr<ViewportController> controllerW) + : MeasureTool(controllerW) #if ORTHANC_STONE_ENABLE_OUTLINED_TEXT == 1 , layerHolder_(boost::make_shared<LayerHolder>(controllerW, 1, 5)) #else @@ -148,7 +148,7 @@ const PointerEvent & e); */ boost::shared_ptr<EditLineMeasureTracker> editLineMeasureTracker( - new EditLineMeasureTracker(shared_from_this(), GetBroker(), GetController(), e)); + new EditLineMeasureTracker(shared_from_this(), GetController(), e)); return editLineMeasureTracker; }
--- a/Framework/Scene2DViewport/LineMeasureTool.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/LineMeasureTool.h Fri Nov 29 11:03:41 2019 +0100 @@ -35,10 +35,10 @@ namespace OrthancStone { - class LineMeasureTool : public MeasureTool, public boost::enable_shared_from_this<LineMeasureTool> + class LineMeasureTool : public MeasureTool { public: - LineMeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW); + LineMeasureTool(boost::weak_ptr<ViewportController> controllerW); ~LineMeasureTool();
--- a/Framework/Scene2DViewport/MeasureTool.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/MeasureTool.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -28,14 +28,6 @@ namespace OrthancStone { - MeasureTool::~MeasureTool() - { - // if the controller is dead, let's not bother. - boost::shared_ptr<ViewportController> controller = controllerW_.lock(); - if (controller) - controller->Unregister(this); - } - void MeasureTool::Enable() { enabled_ = true; @@ -79,15 +71,13 @@ #endif } - MeasureTool::MeasureTool(MessageBroker& broker, + MeasureTool::MeasureTool( boost::weak_ptr<ViewportController> controllerW) - : IObserver(broker) - , controllerW_(controllerW) + : controllerW_(controllerW) , enabled_(true) { - GetController()->RegisterObserverCallback( - new Callable<MeasureTool, ViewportController::SceneTransformChanged> - (*this, &MeasureTool::OnSceneTransformChanged)); + // TODO => Move this out of constructor + Register<ViewportController::SceneTransformChanged>(*GetController(), &MeasureTool::OnSceneTransformChanged); }
--- a/Framework/Scene2DViewport/MeasureTool.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/MeasureTool.h Fri Nov 29 11:03:41 2019 +0100 @@ -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 <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> #include <vector> @@ -38,10 +38,12 @@ class IFlexiblePointerTracker; class MeasureToolMemento; - class MeasureTool : public IObserver + class MeasureTool : public ObserverBase<MeasureTool> { public: - virtual ~MeasureTool(); + virtual ~MeasureTool() + { + } /** Enabled tools are rendered in the scene. @@ -111,7 +113,7 @@ virtual std::string GetDescription() = 0; protected: - MeasureTool(MessageBroker& broker, boost::weak_ptr<ViewportController> controllerW); + MeasureTool(boost::weak_ptr<ViewportController> controllerW); /** The measuring tool may exist in a standalone fashion, without any available
--- a/Framework/Scene2DViewport/ViewportController.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/ViewportController.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -30,10 +30,8 @@ namespace OrthancStone { ViewportController::ViewportController(boost::weak_ptr<UndoStack> undoStackW, - MessageBroker& broker, IViewport& viewport) - : IObservable(broker) - , undoStackW_(undoStackW) + : undoStackW_(undoStackW) , canvasToSceneFactor_(0.0) , viewport_(viewport) {
--- a/Framework/Scene2DViewport/ViewportController.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Scene2DViewport/ViewportController.h Fri Nov 29 11:03:41 2019 +0100 @@ -81,7 +81,6 @@ SceneTransformChanged, ViewportController); ViewportController(boost::weak_ptr<UndoStack> undoStackW, - MessageBroker& broker, IViewport& viewport);
--- a/Framework/StoneEnumerations.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/StoneEnumerations.h Fri Nov 29 11:03:41 2019 +0100 @@ -122,6 +122,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,
--- a/Framework/StoneInitialization.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/StoneInitialization.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -21,20 +21,53 @@ #include "StoneInitialization.h" -#include <Core/OrthancException.h> - #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 <QCoreApplication> +#endif + #if ORTHANC_ENABLE_CURL == 1 -#include <Core/HttpClient.h> +# include <Core/HttpClient.h> +#endif + +#if ORTHANC_ENABLE_DCMTK == 1 +# include <Core/DicomParsing/FromDcmtkBridge.h> #endif +#include "Toolbox/LinearAlgebra.h" + +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +#include <locale> + + namespace OrthancStone { #if ORTHANC_ENABLE_LOGGING_PLUGIN == 1 @@ -49,14 +82,91 @@ 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() { @@ -64,10 +174,18 @@ 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(); } }
--- a/Framework/Toolbox/CoordinateSystem3D.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Toolbox/CoordinateSystem3D.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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; + } }
--- a/Framework/Toolbox/CoordinateSystem3D.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Toolbox/CoordinateSystem3D.h Fri Nov 29 11:03:41 2019 +0100 @@ -22,6 +22,7 @@ #pragma once #include "LinearAlgebra.h" +#include "../Scene2D/ScenePoint2D.h" #include <Plugins/Samples/Common/IDicomDataset.h> @@ -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); }; }
--- a/Framework/Toolbox/DicomInstanceParameters.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Toolbox/DicomInstanceParameters.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -265,7 +265,7 @@ void DicomInstanceParameters::Data::ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image, - bool useDouble) const + bool useDouble) const { if (image.GetFormat() != Orthanc::PixelFormat_Float32) { @@ -455,4 +455,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; + } + } }
--- a/Framework/Toolbox/DicomInstanceParameters.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Toolbox/DicomInstanceParameters.h Fri Nov 29 11:03:41 2019 +0100 @@ -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); + } }; }
--- a/Framework/Toolbox/DicomStructureSet.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Toolbox/DicomStructureSet.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -27,12 +27,11 @@ #include <Core/Logging.h> #include <Core/OrthancException.h> #include <Core/Toolbox.h> -#include <Plugins/Samples/Common/FullOrthancDataset.h> #include <Plugins/Samples/Common/DicomDatasetReader.h> #if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4244) +# pragma warning(push) +# pragma warning(disable:4244) #endif #include <limits> @@ -43,9 +42,14 @@ #include <boost/geometry/multi/geometries/multi_polygon.hpp> #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<double> BoostPoint; typedef boost::geometry::model::polygon<BoostPoint> BoostPolygon; typedef boost::geometry::model::multi_polygon<BoostPolygon> 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<int>(structures_[i].red_) << "," - << static_cast<int>(structures_[i].green_) << "," - << static_cast<int>(structures_[i].blue_) << ")"; + << "\" with interpretation \"" << structures_[i].interpretation_ + << "\" containing " << countSlices << " slices (color: " + << static_cast<int>(structures_[i].red_) << "," + << static_cast<int>(structures_[i].green_) << "," + << static_cast<int>(structures_[i].blue_) << ")"; // These temporary variables avoid allocating many vectors in the loop below OrthancPlugins::DicomPath countPointsPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, @@ -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<Point2D> >& polygons, - const Structure& structure, - const CoordinateSystem3D& slice) const + bool DicomStructureSet::ProjectStructure( +#if USE_BOOST_UNION_FOR_POLYGONS == 1 + std::vector< std::vector<Point2D> >& polygons, #else - bool DicomStructureSet::ProjectStructure(std::vector< std::pair<Point2D, Point2D> >& segments, + std::vector< std::pair<Point2D, Point2D> >& 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<Point2D>()); 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<BoostPolygon> 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<std::pair<RtStructRectangleInSlab, double> > 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<float>(x1), - static_cast<float>(y1), - static_cast<float>(x2), - static_cast<float>(y2)),curZ)); + static_cast<float>(x1), + static_cast<float>(y1), + static_cast<float>(x2), + static_cast<float>(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<Point2D> > polygons; + if (ProjectStructure(polygons, structureIndex, plane)) + { + for (size_t j = 0; j < polygons.size(); j++) + { + std::vector<ScenePoint2D> 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<Point2D, Point2D> > segments; + + if (ProjectStructure(segments, structureIndex, plane)) + { + for (size_t j = 0; j < segments.size(); j++) + { + std::vector<ScenePoint2D> 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 + } }
--- a/Framework/Toolbox/DicomStructureSet.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Toolbox/DicomStructureSet.h Fri Nov 29 11:03:41 2019 +0100 @@ -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 <Core/DicomParsing/ParsedDicomFile.h> +#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<Point2D> >& polygons, - const Structure& structure, - const CoordinateSystem3D& slice) const; + bool ProjectStructure( +#if USE_BOOST_UNION_FOR_POLYGONS == 1 + std::vector< std::vector<Point2D> >& polygons, #else - bool ProjectStructure(std::vector< std::pair<Point2D, Point2D> >& segments, + std::vector< std::pair<Point2D, Point2D> >& 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<Point2D> >& 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<Point2D, Point2D> >& 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)); + } }; }
--- a/Framework/Toolbox/LinearAlgebra.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Toolbox/LinearAlgebra.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -22,6 +22,7 @@ #include "LinearAlgebra.h" #include "../StoneException.h" +#include "GenericToolbox.h" #include <Core/Logging.h> #include <Core/OrthancException.h> @@ -32,6 +33,7 @@ #include <stdio.h> #include <iostream> +#include <cstdlib> 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<double>()". 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<double>(items[i]);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/ParsedDicomCache.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,149 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "ParsedDicomCache.h" + +namespace OrthancStone +{ + class ParsedDicomCache::Item : public Orthanc::ICacheable + { + private: + boost::mutex mutex_; + std::auto_ptr<Orthanc::ParsedDicomFile> 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); + } + } + + boost::mutex& GetMutex() + { + return mutex_; + } + + 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<std::string>(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<Item&>(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(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/ParsedDicomCache.h Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,80 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <Core/Cache/MemoryObjectCache.h> +#include <Core/DicomParsing/ParsedDicomFile.h> + +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; + }; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/ParsedDicomDataset.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,104 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You 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 "ParsedDicomDataset.h" + +#include <dcmtk/dcmdata/dcfilefo.h> + +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; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/ParsedDicomDataset.h Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,46 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <Core/DicomParsing/ParsedDicomFile.h> +#include <Plugins/Samples/Common/IDicomDataset.h> + +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; + }; +}
--- a/Framework/Toolbox/SlicesSorter.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Toolbox/SlicesSorter.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<double>(i) * reference.GetNormal(); + OrthancStone::Vector p = reference.GetOrigin() + spacing * static_cast<double>(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; }
--- a/Framework/Toolbox/SlicesSorter.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Toolbox/SlicesSorter.h Fri Nov 29 11:03:41 2019 +0100 @@ -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;
--- a/Framework/Viewport/IViewport.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Viewport/IViewport.h Fri Nov 29 11:03:41 2019 +0100 @@ -51,4 +51,3 @@ virtual const ICompositor& GetCompositor() const = 0; }; } -
--- a/Framework/Viewport/SdlWindow.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Viewport/SdlWindow.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -31,6 +31,8 @@ #endif // WIN32 +#include <SDL_render.h> +#include <SDL_video.h> #include <SDL.h> namespace OrthancStone
--- a/Framework/Viewport/SdlWindow.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Viewport/SdlWindow.h Fri Nov 29 11:03:41 2019 +0100 @@ -23,18 +23,22 @@ #if ORTHANC_ENABLE_SDL == 1 -#include <SDL_render.h> -#include <SDL_video.h> #include <boost/noncopyable.hpp> +// 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,7 @@ unsigned int GetHeight() const; - void Render(SDL_Surface* surface); + void Render(struct SDL_Surface* surface); void ToggleMaximize();
--- a/Framework/Viewport/ViewportBase.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Viewport/ViewportBase.h Fri Nov 29 11:03:41 2019 +0100 @@ -49,6 +49,5 @@ const_cast<IViewport*>(static_cast<const IViewport*>(this)); return mutableThis->GetCompositor(); } - }; }
--- a/Framework/Volumes/DicomVolumeImage.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Volumes/DicomVolumeImage.h Fri Nov 29 11:03:41 2019 +0100 @@ -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
--- a/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -45,8 +45,7 @@ revision_(volume_.GetRevision()) { valid_ = (volume_.HasDicomParameters() && - volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, - cuttingPlane)); + volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane)); } @@ -82,21 +81,6 @@ ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_); texture.reset(dynamic_cast<TextureBaseSceneLayer*> (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters))); - - // <DEBUG-BLOCK> -#if 0 - Orthanc::JpegWriter writer; - writer.SetQuality(60); - static int index = 0; - std::string filePath = "C:\\temp\\sliceReader_P"; - filePath += boost::lexical_cast<std::string>(projection_); - filePath += "_I"; - filePath += boost::lexical_cast<std::string>(index); - filePath += ".jpg"; - index++; - writer.WriteToFile(filePath, reader.GetAccessor()); -#endif - // <END-OF-DEBUG-BLOCK> } const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_); @@ -105,53 +89,11 @@ cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX()); - // <DEBUG-BLOCK> -#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 - // <END-OF-DEBUG-BLOCK> - -#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; @@ -164,19 +106,6 @@ Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_); texture->SetPixelSpacing(tmp[0], tmp[1]); - // <DEBUG-BLOCK> - { - //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(); - } - // <END-OF-DEBUG-BLOCK> - return texture.release(); }
--- a/Framework/Volumes/IVolumeSlicer.cpp~ Thu Nov 28 18:28:15 2019 +0100 +++ /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-2019 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -namespace OrthancStone -{ - /** - This interface is implemented by objects representing 3D volume data and - that are able to return an object that: - - represent a slice of their data - - are able to create the corresponding slice visual representation. - */ - class IVolumeSlicer : public boost::noncopyable - { - public: - /** - This interface is implemented by objects representing a slice of - volume data and that are able to create a 2D layer to display a this - slice. - - The CreateSceneLayer factory method is called with an optional - configurator that possibly impacts the ISceneLayer subclass that is - created (for instance, if a LUT must be applied on the texture when - displaying it) - */ - class IExtractedSlice : public boost::noncopyable - { - public: - virtual ~IExtractedSlice() - { - } - - /** - Invalid slices are created when the data is not ready yet or if the - cut is outside of the available geometry. - */ - virtual bool IsValid() = 0; - - /** - This retrieves the *revision* that gets incremented every time the - underlying object undergoes a mutable operation (that it, changes its - state). - This **must** be a cheap call. - */ - virtual uint64_t GetRevision() = 0; - - /** Creates the slice visual representation */ - virtual ISceneLayer* CreateSceneLayer( - const ILayerStyleConfigurator* configurator, // possibly absent - const CoordinateSystem3D& cuttingPlane) = 0; - }; - - /** - See IExtractedSlice.IsValid() - */ - class InvalidSlice : public IExtractedSlice - { - public: - virtual bool IsValid() - { - return false; - } - - virtual uint64_t GetRevision() - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, - const CoordinateSystem3D& cuttingPlane) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - }; - - - virtual ~IVolumeSlicer() - { - } - - /** - This method is implemented by the objects representing volumetric data - and must returns an IExtractedSlice subclass that contains all the data - needed to, later on, create its visual representation through - CreateSceneLayer. - Subclasses a.o.: - - InvalidSlice, - - DicomVolumeImageMPRSlicer::Slice, - - DicomVolumeImageReslicer::Slice - - DicomStructureSetLoader::Slice - */ - virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0; - }; -}
--- a/Framework/Volumes/IVolumeSlicer.h~ Thu Nov 28 18:28:15 2019 +0100 +++ /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-2019 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -namespace OrthancStone -{ - /** - This interface is implemented by objects representing 3D volume data and - that are able to return an object that: - - represent a slice of their data - - are able to create the corresponding slice visual representation. - */ - class IVolumeSlicer : public boost::noncopyable - { - public: - /** - This interface is implemented by objects representing a slice of - volume data and that are able to create a 2D layer to display a this - slice. - - The CreateSceneLayer factory method is called with an optional - configurator that possibly impacts the ISceneLayer subclass that is - created (for instance, if a LUT must be applied on the texture when - displaying it) - */ - class IExtractedSlice : public boost::noncopyable - { - public: - virtual ~IExtractedSlice() - { - } - - /** - Invalid slices are created when the data is not ready yet or if the - cut is outside of the available geometry. - */ - virtual bool IsValid() = 0; - - /** - This retrieves the *revision* that gets incremented every time the - underlying object undergoes a mutable operation (that it, changes its - state). - This **must** be a cheap call. - */ - virtual uint64_t GetRevision() = 0; - - /** Creates the slice visual representation */ - virtual ISceneLayer* CreateSceneLayer( - const ILayerStyleConfigurator* configurator, // possibly absent - const CoordinateSystem3D& cuttingPlane) = 0; - }; - - /** - See IExtractedSlice.IsValid() - */ - class InvalidSlice : public IExtractedSlice - { - public: - virtual bool IsValid() - { - return false; - } - - virtual uint64_t GetRevision() - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, - const CoordinateSystem3D& cuttingPlane) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - }; - - - virtual ~IVolumeSlicer() - { - } - - /** - This method is implemented by the objects representing volumetric data - and must returns an IExtractedSlice subclass that contains all the data - needed to, later on, create its visual representation through - CreateSceneLayer. - Subclasses a.o.: - - InvalidSlice, - - DicomVolumeImageMPRSlicer::Slice, - - DicomVolumeImageReslicer::Slice - - DicomStructureSetLoader::Slice - */ - virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0; - }; -}
--- a/Framework/Volumes/VolumeImageGeometry.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Framework/Volumes/VolumeImageGeometry.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<unsigned int>(std::floor(z)); - if (d >= projectionDepth) - { - return false; - } else { - slice = d; - return true; + unsigned int d = static_cast<unsigned int>(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<double>(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<double>(z) * dim[2] * normal); return plane; }
--- a/Platforms/Generic/DelayedCallCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/DelayedCallCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -26,13 +26,11 @@ namespace Deprecated { - DelayedCallCommand::DelayedCallCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback, // takes ownership + DelayedCallCommand::DelayedCallCommand(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback, // takes ownership unsigned int timeoutInMs, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context ) : - IObservable(broker), callback_(callback), payload_(payload), context_(context),
--- a/Platforms/Generic/DelayedCallCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/DelayedCallCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -42,8 +42,7 @@ unsigned int timeoutInMs_; public: - DelayedCallCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback, // takes ownership + DelayedCallCommand(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback, // takes ownership unsigned int timeoutInMs, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context
--- a/Platforms/Generic/OracleDelayedCallExecutor.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/OracleDelayedCallExecutor.h Fri Nov 29 11:03:41 2019 +0100 @@ -36,10 +36,8 @@ OrthancStone::NativeStoneApplicationContext& context_; public: - OracleDelayedCallExecutor(OrthancStone::MessageBroker& broker, - Oracle& oracle, + OracleDelayedCallExecutor(Oracle& oracle, OrthancStone::NativeStoneApplicationContext& context) : - IDelayedCallExecutor(broker), oracle_(oracle), context_(context) { @@ -48,7 +46,7 @@ virtual void Schedule(OrthancStone::MessageHandler<IDelayedCallExecutor::TimeoutMessage>* callback, unsigned int timeoutInMs = 1000) { - oracle_.Submit(new DelayedCallCommand(broker_, callback, timeoutInMs, NULL, context_)); + oracle_.Submit(new DelayedCallCommand(callback, timeoutInMs, NULL, context_)); } }; }
--- a/Platforms/Generic/OracleWebService.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/OracleWebService.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -35,13 +35,11 @@ OrthancStone::NativeStoneApplicationContext& context_; public: - WebServiceCachedGetCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership + WebServiceCachedGetCommand(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedMessage, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context ) : - IObservable(broker), successCallback_(successCallback), payload_(payload), cachedMessage_(cachedMessage), @@ -75,7 +73,7 @@ Orthanc::IDynamicObject* payload, // takes ownership OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback) { - oracle_.Submit(new WebServiceCachedGetCommand(GetBroker(), successCallback, cachedMessage, payload, context_)); + oracle_.Submit(new WebServiceCachedGetCommand(successCallback, cachedMessage, payload, context_)); }
--- a/Platforms/Generic/OracleWebService.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/OracleWebService.h Fri Nov 29 11:03:41 2019 +0100 @@ -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) @@ -62,7 +60,7 @@ OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* 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, @@ -72,7 +70,7 @@ OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* 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: @@ -83,7 +81,7 @@ OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* 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<BaseWebService::CachedHttpRequestSuccessMessage> cachedHttpMessage,
--- a/Platforms/Generic/WebServiceCommandBase.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/WebServiceCommandBase.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -25,8 +25,7 @@ namespace Deprecated { - WebServiceCommandBase::WebServiceCommandBase(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, + WebServiceCommandBase::WebServiceCommandBase(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, const Orthanc::WebServiceParameters& parameters, const std::string& url, @@ -34,7 +33,6 @@ unsigned int timeoutInSeconds, Orthanc::IDynamicObject* payload /* takes ownership */, OrthancStone::NativeStoneApplicationContext& context) : - IObservable(broker), successCallback_(successCallback), failureCallback_(failureCallback), parameters_(parameters),
--- a/Platforms/Generic/WebServiceCommandBase.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/WebServiceCommandBase.h Fri Nov 29 11:03:41 2019 +0100 @@ -51,8 +51,7 @@ unsigned int timeoutInSeconds_; public: - WebServiceCommandBase(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership + WebServiceCommandBase(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url,
--- a/Platforms/Generic/WebServiceDeleteCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/WebServiceDeleteCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -25,8 +25,7 @@ namespace Deprecated { - WebServiceDeleteCommand::WebServiceDeleteCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership + WebServiceDeleteCommand::WebServiceDeleteCommand(OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestErrorMessage>* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url, @@ -34,7 +33,7 @@ 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) { }
--- a/Platforms/Generic/WebServiceDeleteCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/WebServiceDeleteCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -28,8 +28,7 @@ class WebServiceDeleteCommand : public WebServiceCommandBase { public: - WebServiceDeleteCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership + WebServiceDeleteCommand(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url,
--- a/Platforms/Generic/WebServiceGetCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/WebServiceGetCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -25,9 +25,7 @@ namespace Deprecated { - - WebServiceGetCommand::WebServiceGetCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership + WebServiceGetCommand::WebServiceGetCommand(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url, @@ -35,7 +33,7 @@ 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) { }
--- a/Platforms/Generic/WebServiceGetCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/WebServiceGetCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -28,8 +28,7 @@ class WebServiceGetCommand : public WebServiceCommandBase { public: - WebServiceGetCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership + WebServiceGetCommand(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url,
--- a/Platforms/Generic/WebServicePostCommand.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/WebServicePostCommand.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -25,8 +25,7 @@ namespace Deprecated { - WebServicePostCommand::WebServicePostCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership + WebServicePostCommand::WebServicePostCommand(OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership OrthancStone::MessageHandler<Deprecated::IWebService::HttpRequestErrorMessage>* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url, @@ -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) { }
--- a/Platforms/Generic/WebServicePostCommand.h Thu Nov 28 18:28:15 2019 +0100 +++ b/Platforms/Generic/WebServicePostCommand.h Fri Nov 29 11:03:41 2019 +0100 @@ -31,8 +31,7 @@ std::string body_; public: - WebServicePostCommand(OrthancStone::MessageBroker& broker, - OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership + WebServicePostCommand(OrthancStone::MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership OrthancStone::MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& url,
--- a/Resources/CMake/OrthancStoneConfiguration.cmake Thu Nov 28 18:28:15 2019 +0100 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Fri Nov 29 11:03:41 2019 +0100 @@ -250,10 +250,6 @@ ## 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}/Platforms/Generic/WebServiceCommandBase.cpp @@ -265,46 +261,21 @@ ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleDelayedCallExecutor.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 ) 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/Viewport/SdlViewport.cpp ) - - 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,15 +302,45 @@ 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 + ) + 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 (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_WASM) + list(APPEND APPLICATIONS_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/Wasm/StartupParametersBuilder.cpp + ) + endif() + list(APPEND ORTHANC_STONE_SOURCES + ${ORTHANC_STONE_ROOT}/Applications/IStoneApplication.h ${ORTHANC_STONE_ROOT}/Applications/StoneApplicationContext.cpp ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/CircleMeasureTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Layers/ColorFrameRenderer.cpp @@ -405,10 +406,20 @@ 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.cpp ${ORTHANC_STONE_ROOT}/Framework/Messages/LockingEmitter.h ${ORTHANC_STONE_ROOT}/Framework/Oracle/ThreadedOracle.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/GenericOracleRunner.cpp ) endif() @@ -459,15 +470,14 @@ ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.cpp - ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.cpp ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h - ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h - ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageForwarder.cpp ${ORTHANC_STONE_ROOT}/Framework/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/ColorTextureSceneLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Scene2D/FloatTextureSceneLayer.cpp @@ -564,6 +574,8 @@ ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Extent2D.h ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/FiniteProjectiveCamera.h + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GenericToolbox.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GenericToolbox.h ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.h ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp @@ -580,8 +592,6 @@ ${ORTHANC_STONE_ROOT}/Framework/Toolbox/TextRenderer.h ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.h - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GenericToolbox.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GenericToolbox.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/IViewport.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/ViewportBase.h
--- a/Resources/CMake/QtConfiguration.cmake Thu Nov 28 18:28:15 2019 +0100 +++ b/Resources/CMake/QtConfiguration.cmake Fri Nov 29 11:03:41 2019 +0100 @@ -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 "<?xml ...?>" - # 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 "<?xml ...?>" + # 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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Conventions.txt Fri Nov 29 11:03:41 2019 +0100 @@ -0,0 +1,87 @@ + +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::auto_ptr<>" and "boost::shared_ptr<>" (*not* + "std::shared_ptr<>"). + +* 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::auto_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<>".
--- a/UnitTestsSources/GenericToolboxTests.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/UnitTestsSources/GenericToolboxTests.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -20,7 +20,7 @@ #include <Framework/Toolbox/GenericToolbox.h> -#include <boost/chrono.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> #include <boost/lexical_cast.hpp> #include "gtest/gtest.h" @@ -3878,19 +3878,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<boost::chrono::microseconds>(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 +3901,8 @@ ok = false; } } - boost::chrono::system_clock::time_point end = boost::chrono::system_clock::now(); - boost::chrono::microseconds elapsed = - boost::chrono::duration_cast<boost::chrono::microseconds>(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 +4091,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<boost::chrono::microseconds>(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 +4114,8 @@ ok = false; } } - boost::chrono::system_clock::time_point end = boost::chrono::system_clock::now(); - boost::chrono::microseconds elapsed = - boost::chrono::duration_cast<boost::chrono::microseconds>(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;
--- a/UnitTestsSources/TestMessageBroker.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/UnitTestsSources/TestMessageBroker.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<MyObserver> { 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<MyObservable::MyCustomMessage>(broker, *this)); - } }; } TEST(MessageBroker, TestPermanentConnectionSimpleUseCase) { - MessageBroker broker; - MyObservable observable(broker); - MyObserver observer(broker); + MyObservable observable; + boost::shared_ptr<MyObserver> observer(new MyObserver); // create a permanent connection between an observable and an observer - observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage)); + observer->Register<MyObservable::MyCustomMessage>(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<MyObserver, MyObservable::MyCustomMessage>(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<MyObserver> observer(new MyObserver); // create a permanent connection between an observable and an observer - observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(*observer, &MyObserver::HandleCompletedMessage)); + observer->Register<MyObservable::MyCustomMessage>(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<MyObserver, MyObservable::MyCustomMessage>(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<MyObserver, MyObservable::MyCustomMessage>(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<MyObservable::MyCustomMessage>(*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<MyObservable::MyCustomMessage>(*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
--- a/UnitTestsSources/UnitTestsMain.cpp Thu Nov 28 18:28:15 2019 +0100 +++ b/UnitTestsSources/UnitTestsMain.cpp Fri Nov 29 11:03:41 2019 +0100 @@ -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<float>(z)) / static_cast<float>(g.GetDepth()); // Z-center of the voxel + + for (unsigned int y = 0; y < g.GetHeight(); y++) + { + const float yy = (0.5f + static_cast<float>(y)) / static_cast<float>(g.GetHeight()); // Y-center of the voxel + + for (unsigned int x = 0; x < g.GetWidth(); x++) + { + const float xx = (0.5f + static_cast<float>(x)) / static_cast<float>(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<double>(x) * sx, + static_cast<double>(y) * sy) + + z * sz * g.GetAxialGeometry().GetNormal()); + ASSERT_TRUE(IsEqualVectorL1(p, q)); + + q = (g.GetCoronalGeometry().MapSliceToWorldCoordinates( + static_cast<double>(x) * sx, + static_cast<double>(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<double>(y) * sy, + static_cast<double>(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<double>(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<double>(i) * + (-s.GetNormal()) * g.GetVoxelDimensions(projection)[2])); + } + else + { + ASSERT_TRUE(IsEqualVectorL1(plane.GetOrigin(), s.GetOrigin() + static_cast<double>(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_FLOAT_EQ(1.2f, v[0]); + + ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "-1.2e+2")); + ASSERT_EQ(1u, v.size()); + ASSERT_FLOAT_EQ(-120.0f, v[0]); + + ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "-1e-2\\2")); + ASSERT_EQ(2u, v.size()); + ASSERT_FLOAT_EQ(-0.01f, v[0]); + ASSERT_FLOAT_EQ(2.0f, v[1]); + + ASSERT_TRUE(OrthancStone::LinearAlgebra::ParseVector(v, "1.3671875\\1.3671875")); + ASSERT_EQ(2u, v.size()); + ASSERT_FLOAT_EQ(1.3671875, v[0]); + ASSERT_FLOAT_EQ(1.3671875, v[1]); +} + + int main(int argc, char **argv) { Orthanc::Logging::Initialize();