# HG changeset patch # User am@osimis.io # Date 1539682200 -7200 # Node ID 37ab9d83dc9ba4b8221a2b0b6931910e9199beb3 # Parent 29a79b8c3d3934f3694d9d82a19618400267693c reactivate SingleFrameApplication sample + Added SingleFrameEditorApplication (SDL only) diff -r 29a79b8c3d39 -r 37ab9d83dc9b Applications/Samples/CMakeLists.txt --- a/Applications/Samples/CMakeLists.txt Tue Oct 16 08:59:23 2018 +0200 +++ b/Applications/Samples/CMakeLists.txt Tue Oct 16 11:30:00 2018 +0200 @@ -123,12 +123,13 @@ #BuildSingleFileSample(OrthancStoneEmpty EmptyApplication.h 1) #BuildSingleFileSample(OrthancStoneTestPattern TestPatternApplication.h 2) -#BuildSingleFileSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) +BuildSingleFileSample(OrthancStoneSingleFrame SingleFrameApplication.h 3) #BuildSingleFileSample(OrthancStoneSingleVolume SingleVolumeApplication.h 4) #BuildSingleFileSample(OrthancStoneBasicPetCtFusion 5) #BuildSingleFileSample(OrthancStoneSynchronizedSeries 6) #BuildSingleFileSample(OrthancStoneLayoutPetCtFusion 7) BuildSingleFileSample(OrthancStoneSimpleViewerSingleFile SimpleViewerApplicationSingleFile.h 8) # we keep that one just as a sample before we convert another sample to this pattern +BuildSingleFileSample(OrthancStoneSingleFrameEditor SingleFrameEditorApplication.h 9) ##### SimpleViewer sample (Qt and WASM only) ####### diff -r 29a79b8c3d39 -r 37ab9d83dc9b Applications/Samples/SampleList.h --- a/Applications/Samples/SampleList.h Tue Oct 16 08:59:23 2018 +0200 +++ b/Applications/Samples/SampleList.h Tue Oct 16 11:30:00 2018 +0200 @@ -32,6 +32,10 @@ #include "SimpleViewerApplicationSingleFile.h" typedef OrthancStone::Samples::SimpleViewerApplication SampleApplication; +#elif ORTHANC_STONE_SAMPLE == 9 +#include "SingleFrameEditorApplication.h" +typedef OrthancStone::Samples::SingleFrameEditorApplication SampleApplication; + #else #error Please set the ORTHANC_STONE_SAMPLE macro #endif diff -r 29a79b8c3d39 -r 37ab9d83dc9b Applications/Samples/SingleFrameApplication.h --- a/Applications/Samples/SingleFrameApplication.h Tue Oct 16 08:59:23 2018 +0200 +++ b/Applications/Samples/SingleFrameApplication.h Tue Oct 16 11:30:00 2018 +0200 @@ -34,7 +34,7 @@ { class SingleFrameApplication : public SampleApplicationBase, - private ILayerSource::IObserver + public IObserver { private: class Interactor : public IWorldSceneInteractor @@ -51,6 +51,7 @@ virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, const ViewportGeometry& view, MouseButton button, + KeyboardModifiers modifiers, double x, double y, IStatusBar* statusBar) @@ -148,7 +149,7 @@ slice_ = index; #if 1 - widget_->SetSlice(source_->GetSlice(slice_).GetGeometry()); + mainWidget_->SetSlice(source_->GetSlice(slice_).GetGeometry()); #else // TEST for scene extents - Rotate the axes double a = 15.0 / 180.0 * M_PI; @@ -169,46 +170,29 @@ } - virtual void NotifyGeometryReady(const ILayerSource& source) + void OnMainWidgetGeometryReady(const ILayerSource::GeometryReadyMessage& message) { // Once the geometry of the series is downloaded from Orthanc, - // display its first slice, and adapt the viewport to fit this + // display its middle slice, and adapt the viewport to fit this // slice - if (source_ == &source) + if (source_ == &message.origin_) { SetSlice(source_->GetSliceCount() / 2); } - widget_->SetDefaultView(); - } - - virtual void NotifyGeometryError(const ILayerSource& source) - { + mainWidget_->SetDefaultView(); } - virtual void NotifyContentChange(const ILayerSource& source) - { - } + LayerWidget* mainWidget_; // ownership is transfered to the application context + std::unique_ptr mainWidgetInteractor_; + std::unique_ptr orthancApiClient_; - virtual void NotifySliceChange(const ILayerSource& source, - const Slice& slice) - { - } - - virtual void NotifyLayerReady(std::auto_ptr& layer, - const ILayerSource& source, - const CoordinateSystem3D& slice, - bool isError) - { - } + const OrthancFrameLayerSource* source_; + unsigned int slice_; - LayerWidget* widget_; - const OrthancFrameLayerSource* source_; - unsigned int slice_; - public: - SingleFrameApplication() : - widget_(NULL), + SingleFrameApplication(MessageBroker& broker) : + IObserver(broker), source_(NULL), slice_(0) { @@ -229,11 +213,14 @@ options.add(generic); } - virtual void Initialize(IStatusBar& statusBar, + virtual void Initialize(StoneApplicationContext* context, + IStatusBar& statusBar, const boost::program_options::variables_map& parameters) { using namespace OrthancStone; + context_ = context; + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); if (parameters.count("instance") != 1) @@ -245,17 +232,14 @@ std::string instance = parameters["instance"].as(); int frame = parameters["frame"].as(); - std::auto_ptr widget(new LayerWidget); + orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService())); + mainWidget_ = new LayerWidget(broker_, "main-widget"); -#if 1 - std::auto_ptr layer - (new OrthancFrameLayerSource(context_->GetWebService())); - //layer->SetImageQuality(SliceImageQuality_Jpeg50); + std::auto_ptr layer(new OrthancFrameLayerSource(broker_, *orthancApiClient_)); + source_ = layer.get(); layer->LoadFrame(instance, frame); - //layer->LoadSeries("6f1b492a-e181e200-44e51840-ef8db55e-af529ab6"); - layer->Register(*this); - source_ = layer.get(); - widget->AddLayer(layer.release()); + layer->RegisterObserverCallback(new Callable(*this, &SingleFrameApplication::OnMainWidgetGeometryReady)); + mainWidget_->AddLayer(layer.release()); RenderStyle s; @@ -264,53 +248,17 @@ s.interpolation_ = ImageInterpolation_Bilinear; } - //s.drawGrid_ = true; - widget->SetLayerStyle(0, s); -#else - // 0178023P** - // Extent of the CT layer: (-35.068 -20.368) => (34.932 49.632) - std::auto_ptr ct; - ct.reset(new OrthancFrameLayerSource(context_->GetWebService())); - //ct->LoadInstance("c804a1a2-142545c9-33b32fe2-3df4cec0-a2bea6d6", 0); - //ct->LoadInstance("4bd4304f-47478948-71b24af2-51f4f1bc-275b6c1b", 0); // BAD SLICE - //ct->SetImageQuality(SliceImageQuality_Jpeg50); - ct->LoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d"); - - ct->Register(*this); - widget->AddLayer(ct.release()); - - std::auto_ptr pet; - pet.reset(new OrthancFrameLayerSource(context_->GetWebService())); - //pet->LoadInstance("a1c4dc6b-255d27f0-88069875-8daed730-2f5ee5c6", 0); - pet->LoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e"); - pet->Register(*this); - source_ = pet.get(); - widget->AddLayer(pet.release()); + mainWidget_->SetLayerStyle(0, s); + mainWidget_->SetTransmitMouseOver(true); - { - RenderStyle s; - //s.drawGrid_ = true; - s.alpha_ = 1; - widget->SetLayerStyle(0, s); - } + mainWidgetInteractor_.reset(new Interactor(*this)); + mainWidget_->SetInteractor(*mainWidgetInteractor_); + } - { - RenderStyle s; - //s.drawGrid_ = true; - s.SetColor(255, 0, 0); // Draw missing PET layer in red - s.alpha_ = 0.5; - s.applyLut_ = true; - s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; - s.interpolation_ = ImageInterpolation_Bilinear; - widget->SetLayerStyle(1, s); - } -#endif + virtual void Finalize() {} + virtual IWidget* GetCentralWidget() {return mainWidget_;} + }; - widget_ = widget.get(); - widget_->SetTransmitMouseOver(true); - widget_->SetInteractor(context_->AddInteractor(new Interactor(*this))); - context_->SetCentralWidget(widget.release()); - } - }; + } } diff -r 29a79b8c3d39 -r 37ab9d83dc9b Applications/Samples/SingleFrameEditorApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/SingleFrameEditorApplication.h Tue Oct 16 11:30:00 2018 +0200 @@ -0,0 +1,241 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "SampleApplicationBase.h" + +#include "../../Framework/Layers/OrthancFrameLayerSource.h" +#include "../../Framework/Widgets/LayerWidget.h" + +#include + +namespace OrthancStone +{ + namespace Samples + { + class SingleFrameEditorApplication : + public SampleApplicationBase, + public IObserver + { + enum Tools + { + Tools_Crop, + Tools_Windowing, + Tools_Zoom, + Tools_Pan + }; + + enum Actions + { + Actions_Invert, + Actions_RotateLeft, + Actions_RotateRight + }; + + private: + class Interactor : public IWorldSceneInteractor + { + private: + SingleFrameEditorApplication& application_; + + public: + Interactor(SingleFrameEditorApplication& application) : + application_(application) + { + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + MouseButton button, + KeyboardModifiers modifiers, + double x, + double y, + IStatusBar* statusBar) + { + switch (application_.currentTool_) { + case Tools_Crop: + case Tools_Windowing: + case Tools_Zoom: + case Tools_Pan: + // TODO return the right mouse tracker + return NULL; + } + + return NULL; + } + + virtual void MouseOver(CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) + { + if (statusBar != NULL) + { + Vector p = dynamic_cast(widget).GetSlice().MapSliceToWorldCoordinates(x, y); + + char buf[64]; + sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", + p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); + statusBar->SetMessage(buf); + } + } + + virtual void MouseWheel(WorldSceneWidget& widget, + MouseWheelDirection direction, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + } + + virtual void KeyPressed(WorldSceneWidget& widget, + char key, + KeyboardModifiers modifiers, + IStatusBar* statusBar) + { + switch (key) + { + case 's': + widget.SetDefaultView(); + break; + case 'p': + application_.currentTool_ = Tools_Pan; + break; + case 'z': + application_.currentTool_ = Tools_Zoom; + break; + case 'c': + application_.currentTool_ = Tools_Crop; + break; + case 'w': + application_.currentTool_ = Tools_Windowing; + break; + case 'i': + application_.Invert(); + break; + case 'r': + if (modifiers == KeyboardModifiers_None) + application_.Rotate(90); + else + application_.Rotate(-90); + break; + case 'e': + application_.Export(); + break; + default: + break; + } + } + }; + + void OnMainWidgetGeometryReady(const ILayerSource::GeometryReadyMessage& message) + { + mainWidget_->SetDefaultView(); + } + + LayerWidget* mainWidget_; // ownership is transfered to the application context + std::unique_ptr mainWidgetInteractor_; + std::unique_ptr orthancApiClient_; + Tools currentTool_; + + const OrthancFrameLayerSource* source_; + unsigned int slice_; + + public: + SingleFrameEditorApplication(MessageBroker& broker) : + IObserver(broker), + currentTool_(Tools_Zoom), + source_(NULL), + slice_(0) + { + } + + virtual void DeclareStartupOptions(boost::program_options::options_description& options) + { + boost::program_options::options_description generic("Sample options"); + generic.add_options() + ("instance", boost::program_options::value(), + "Orthanc ID of the instance") + ("frame", boost::program_options::value()->default_value(0), + "Number of the frame, for multi-frame DICOM instances") + ; + + options.add(generic); + } + + virtual void Initialize(StoneApplicationContext* context, + IStatusBar& statusBar, + const boost::program_options::variables_map& parameters) + { + using namespace OrthancStone; + + context_ = context; + + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout, \"p\" to pan, \"z\" to zoom, \"c\" to crop, \"i\" to invert, \"w\" to change windowing, \"r\" to rotate cw, \"shift+r\" to rotate ccw"); + + if (parameters.count("instance") != 1) + { + LOG(ERROR) << "The instance ID is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::string instance = parameters["instance"].as(); + int frame = parameters["frame"].as(); + + orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService())); + mainWidget_ = new LayerWidget(broker_, "main-widget"); + + std::auto_ptr layer(new OrthancFrameLayerSource(broker_, *orthancApiClient_)); + source_ = layer.get(); + layer->LoadFrame(instance, frame); + layer->RegisterObserverCallback(new Callable(*this, &SingleFrameEditorApplication::OnMainWidgetGeometryReady)); + mainWidget_->AddLayer(layer.release()); + + mainWidget_->SetTransmitMouseOver(true); + + mainWidgetInteractor_.reset(new Interactor(*this)); + mainWidget_->SetInteractor(*mainWidgetInteractor_); + } + + virtual void Finalize() {} + virtual IWidget* GetCentralWidget() {return mainWidget_;} + + void Invert() + { + // TODO + } + + void Rotate(int degrees) + { + // TODO + } + + void Export() + { + // TODO: export dicom file to a temporary file + } + }; + + + } +}