# HG changeset patch # User Sebastien Jodogne # Date 1495051389 -7200 # Node ID 298f375dcb683ac79cffcea42bf8047f497e0a55 # Parent 885932a893dec1f8cb661328350a276494b31fb4 LayerWidget diff -r 885932a893de -r 298f375dcb68 Applications/BasicApplicationContext.cpp --- a/Applications/BasicApplicationContext.cpp Tue May 16 22:12:41 2017 +0200 +++ b/Applications/BasicApplicationContext.cpp Wed May 17 22:03:09 2017 +0200 @@ -141,6 +141,8 @@ stopped_ = false; updateThread_ = boost::thread(UpdateThread, this); } + + viewport_.Start(); } diff -r 885932a893de -r 298f375dcb68 Applications/IBasicApplication.cpp --- a/Applications/IBasicApplication.cpp Tue May 16 22:12:41 2017 +0200 +++ b/Applications/IBasicApplication.cpp Wed May 17 22:03:09 2017 +0200 @@ -210,6 +210,8 @@ * Initialize the application ****************************************************************/ + LOG(WARNING) << "Creating the widgets of the application"; + LogStatusBar statusBar; BasicApplicationContext context(orthanc); diff -r 885932a893de -r 298f375dcb68 Applications/Samples/SingleFrameApplication.h --- a/Applications/Samples/SingleFrameApplication.h Tue May 16 22:12:41 2017 +0200 +++ b/Applications/Samples/SingleFrameApplication.h Wed May 17 22:03:09 2017 +0200 @@ -23,17 +23,45 @@ #include "SampleApplicationBase.h" -#include "../../Framework/Layers/SingleFrameRendererFactory.h" -#include "../../Framework/Widgets/LayeredSceneWidget.h" +#define OLD 0 + +#if OLD == 1 +# include "../../Framework/Layers/SingleFrameRendererFactory.h" +# include "../../Framework/Widgets/LayeredSceneWidget.h" +#else +# include "../../Framework/Layers/OrthancFrameLayerSource.h" +# include "../../Framework/Widgets/LayerWidget.h" +#endif + #include "../../Resources/Orthanc/Core/Logging.h" namespace OrthancStone { namespace Samples { - class SingleFrameApplication : public SampleApplicationBase + class SingleFrameApplication : + public SampleApplicationBase, + public IVolumeSlicesObserver { + private: + LayerWidget* widget_; + public: + SingleFrameApplication() : widget_(NULL) + { + } + + virtual void NotifySlicesAvailable(const ParallelSlices& slices) + { + printf("ICI\n"); + if (widget_ != NULL && + slices.GetSliceCount() > 0) + { + printf("GO\n"); + widget_->SetSlice(slices.GetSlice(0), 1.0 /* TODO */); + } + } + virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) { boost::program_options::options_description generic("Sample options"); @@ -64,6 +92,7 @@ std::string instance = parameters["instance"].as(); int frame = parameters["frame"].as(); +#if OLD == 1 std::auto_ptr renderer; renderer.reset (new SingleFrameRendererFactory(context.GetWebService().GetConnection(), instance, frame)); @@ -80,6 +109,45 @@ } context.SetCentralWidget(widget.release()); + +#else + std::auto_ptr widget(new LayerWidget); + +#if 0 + std::auto_ptr layer + (new OrthancFrameLayerSource(context.GetWebService(), instance, frame)); + layer->SetObserver(*this); + widget->AddLayer(layer.release()); + + if (parameters["smooth"].as()) + { + RenderStyle s; + s.interpolation_ = ImageInterpolation_Linear; + widget->SetLayerStyle(0, s); + } +#else + // 0178023P** + std::auto_ptr layer; + layer.reset(new OrthancFrameLayerSource(context.GetWebService(), "c804a1a2-142545c9-33b32fe2-3df4cec0-a2bea6d6", 0)); + //layer.reset(new OrthancFrameLayerSource(context.GetWebService(), "4bd4304f-47478948-71b24af2-51f4f1bc-275b6c1b", 0)); // BAD SLICE + layer->SetObserver(*this); + widget->AddLayer(layer.release()); + + widget->AddLayer(new OrthancFrameLayerSource(context.GetWebService(), "a1c4dc6b-255d27f0-88069875-8daed730-2f5ee5c6", 0)); + + RenderStyle s; + //s.drawGrid_ = true; + s.alpha_ = 1; + widget->SetLayerStyle(0, s); + s.alpha_ = 0.5; + s.applyLut_ = true; + s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; + widget->SetLayerStyle(1, s); +#endif + + widget_ = widget.get(); + context.SetCentralWidget(widget.release()); +#endif } }; } diff -r 885932a893de -r 298f375dcb68 Framework/Layers/FrameRenderer.cpp --- a/Framework/Layers/FrameRenderer.cpp Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Layers/FrameRenderer.cpp Wed May 17 22:03:09 2017 +0200 @@ -35,7 +35,9 @@ double pixelSpacingY) { bool isOpposite; - if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, viewportSlice.GetNormal(), frameSlice.GetNormal())) + if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, + viewportSlice.GetNormal(), + frameSlice.GetNormal())) { return false; } @@ -145,7 +147,8 @@ if (display_.get() == NULL) { - if (!ComputePixelTransform(transform_, viewportSlice_, frameSlice_, pixelSpacingX_, pixelSpacingY_)) + if (!ComputePixelTransform(transform_, viewportSlice_, frameSlice_, + pixelSpacingX_, pixelSpacingY_)) { return true; } diff -r 885932a893de -r 298f375dcb68 Framework/Layers/ILayerSource.h --- a/Framework/Layers/ILayerSource.h Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Layers/ILayerSource.h Wed May 17 22:03:09 2017 +0200 @@ -36,6 +36,10 @@ { } + // Triggered as soon as the source has enough information to + // answer to "GetExtent()" + virtual void NotifyGeometryReady(ILayerSource& source) = 0; + // Triggered if the extent or the content of the volume has changed virtual void NotifySourceChange(ILayerSource& source) = 0; @@ -67,5 +71,7 @@ const SliceGeometry& viewportSlice) = 0; virtual void ScheduleLayerCreation(const SliceGeometry& viewportSlice) = 0; + + virtual void Start() = 0; }; } diff -r 885932a893de -r 298f375dcb68 Framework/Layers/LayerSourceBase.cpp --- a/Framework/Layers/LayerSourceBase.cpp Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Layers/LayerSourceBase.cpp Wed May 17 22:03:09 2017 +0200 @@ -25,8 +25,26 @@ namespace OrthancStone { + void LayerSourceBase::NotifyGeometryReady() + { + if (!started_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + if (observer_ != NULL) + { + observer_->NotifyGeometryReady(*this); + } + } + void LayerSourceBase::NotifySourceChange() { + if (!started_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + if (observer_ != NULL) { observer_->NotifySourceChange(*this); @@ -35,6 +53,11 @@ void LayerSourceBase::NotifySliceChange(const SliceGeometry& slice) { + if (!started_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + if (observer_ != NULL) { observer_->NotifySliceChange(*this, slice); @@ -42,9 +65,15 @@ } void LayerSourceBase::NotifyLayerReady(ILayerRenderer* layer, - ILayerSource& source, const SliceGeometry& viewportSlice) { + std::auto_ptr tmp(layer); + + if (!started_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + if (layer == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); @@ -52,17 +81,17 @@ if (observer_ != NULL) { - observer_->NotifyLayerReady(layer, *this, viewportSlice); - } - else - { - delete layer; + observer_->NotifyLayerReady(tmp.release(), *this, viewportSlice); } } - void LayerSourceBase::NotifyLayerError(ILayerSource& source, - const SliceGeometry& viewportSlice) + void LayerSourceBase::NotifyLayerError(const SliceGeometry& viewportSlice) { + if (!started_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + if (observer_ != NULL) { observer_->NotifyLayerError(*this, viewportSlice); @@ -71,6 +100,32 @@ void LayerSourceBase::SetObserver(IObserver& observer) { - observer_ = &observer; + if (started_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + if (observer_ == NULL) + { + observer_ = &observer; + } + else + { + // Cannot add more than one observer + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + void LayerSourceBase::Start() + { + if (started_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + started_ = true; + StartInternal(); + } } } diff -r 885932a893de -r 298f375dcb68 Framework/Layers/LayerSourceBase.h --- a/Framework/Layers/LayerSourceBase.h Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Layers/LayerSourceBase.h Wed May 17 22:03:09 2017 +0200 @@ -29,25 +29,37 @@ { private: IObserver* observer_; + bool started_; protected: + bool IsStarted() + { + return started_; + } + + void NotifyGeometryReady(); + void NotifySourceChange(); void NotifySliceChange(const SliceGeometry& slice); // Takes ownership of "layer" (that cannot be "NULL") void NotifyLayerReady(ILayerRenderer* layer, - ILayerSource& source, const SliceGeometry& viewportSlice); - void NotifyLayerError(ILayerSource& source, - const SliceGeometry& viewportSlice); + void NotifyLayerError(const SliceGeometry& viewportSlice); + + virtual void StartInternal() = 0; public: - LayerSourceBase() : observer_(NULL) + LayerSourceBase() : + observer_(NULL), + started_(false) { } virtual void SetObserver(IObserver& observer); + + virtual void Start(); }; } diff -r 885932a893de -r 298f375dcb68 Framework/Layers/OrthancFrameLayerSource.cpp --- a/Framework/Layers/OrthancFrameLayerSource.cpp Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Layers/OrthancFrameLayerSource.cpp Wed May 17 22:03:09 2017 +0200 @@ -65,14 +65,24 @@ unsigned int frame) : orthanc_(orthanc), instanceId_(instanceId), - frame_(frame) + frame_(frame), + frameWidth_(0), + frameHeight_(0), + pixelSpacingX_(1), + pixelSpacingY_(1), + observer2_(NULL) + { + } + + + void OrthancFrameLayerSource::StartInternal() { orthanc_.ScheduleGetRequest(*this, - "/instances/" + instanceId + "/tags", + "/instances/" + instanceId_ + "/tags", new Operation(Content_Tags)); } + - void OrthancFrameLayerSource::SetObserver(IObserver& observer) { LayerSourceBase::SetObserver(observer); @@ -83,11 +93,35 @@ } } + + void OrthancFrameLayerSource::SetObserver(IVolumeSlicesObserver& observer) + { + if (IsStarted()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + if (observer2_ == NULL) + { + observer2_ = &observer; + } + else + { + // Cannot add more than one observer + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + void OrthancFrameLayerSource::NotifyError(const std::string& uri, Orthanc::IDynamicObject* payload) { + std::auto_ptr operation(reinterpret_cast(payload)); + LOG(ERROR) << "Cannot download " << uri; + NotifyLayerError(operation->GetViewportSlice()); } + void OrthancFrameLayerSource::NotifySuccess(const std::string& uri, const void* answer, @@ -107,15 +141,36 @@ DicomFrameConverter converter; converter.ReadParameters(*dataset_); format_ = converter.GetExpectedPixelFormat(); + GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, *dataset_); - NotifySourceChange(); + OrthancPlugins::DicomDatasetReader reader(*dataset_); + if (!reader.GetUnsignedIntegerValue(frameWidth_, OrthancPlugins::DICOM_TAG_COLUMNS) || + !reader.GetUnsignedIntegerValue(frameHeight_, OrthancPlugins::DICOM_TAG_ROWS)) + { + frameWidth_ = 0; + frameHeight_ = 0; + LOG(WARNING) << "Missing tags in a DICOM image: Columns or Rows"; + } + + if (observer2_ != NULL) + { + ParallelSlices slices; + slices.AddSlice(SliceGeometry(*dataset_)); + observer2_->NotifySlicesAvailable(slices); + } + + NotifyGeometryReady(); } else if (operation->GetContent() == Content_Frame) { std::auto_ptr image(new Orthanc::PngReader); image->ReadFromMemory(answer, answerSize); - - if (format_ == Orthanc::PixelFormat_SignedGrayscale16) + + bool ok = (image->GetWidth() == frameWidth_ || + image->GetHeight() == frameHeight_); + + if (ok && + format_ == Orthanc::PixelFormat_SignedGrayscale16) { if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16) { @@ -123,15 +178,23 @@ } else { - NotifyLayerReady(NULL, *this, operation->GetViewportSlice()); + ok = false; } } - - SliceGeometry frameSlice(*dataset_); - NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(), - operation->GetViewportSlice(), - frameSlice, *dataset_, 1, 1, true), - *this, operation->GetViewportSlice()); + + if (ok) + { + SliceGeometry frameSlice(*dataset_); + NotifyLayerReady(FrameRenderer::CreateRenderer(image.release(), + operation->GetViewportSlice(), + frameSlice, *dataset_, + pixelSpacingX_, pixelSpacingY_, true), + operation->GetViewportSlice()); + } + else + { + NotifyLayerError(operation->GetViewportSlice()); + } } else { @@ -146,39 +209,32 @@ double& y2, const SliceGeometry& viewportSlice /* ignored */) { - if (dataset_.get() == NULL) + if (!IsStarted() || + dataset_.get() == NULL) { return false; } else { - // Assume that PixelSpacingX == PixelSpacingY == 1 - - OrthancPlugins::DicomDatasetReader reader(*dataset_); - - unsigned int width, height; - - if (!reader.GetUnsignedIntegerValue(width, OrthancPlugins::DICOM_TAG_COLUMNS) || - !reader.GetUnsignedIntegerValue(height, OrthancPlugins::DICOM_TAG_ROWS)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - x1 = 0; - y1 = 0; - x2 = static_cast(width); - y2 = static_cast(height); - - return true; + SliceGeometry frameSlice(*dataset_); + return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2, + viewportSlice, frameSlice, + frameWidth_, frameHeight_, + pixelSpacingX_, pixelSpacingY_); } } void OrthancFrameLayerSource::ScheduleLayerCreation(const SliceGeometry& viewportSlice) { + if (!IsStarted()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + if (dataset_.get() == NULL) { - NotifyLayerReady(NULL, *this, viewportSlice); + NotifyLayerError(viewportSlice); } else { diff -r 885932a893de -r 298f375dcb68 Framework/Layers/OrthancFrameLayerSource.h --- a/Framework/Layers/OrthancFrameLayerSource.h Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Layers/OrthancFrameLayerSource.h Wed May 17 22:03:09 2017 +0200 @@ -23,13 +23,14 @@ #include "LayerSourceBase.h" #include "../Toolbox/IWebService.h" +#include "../Toolbox/IVolumeSlicesObserver.h" #include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h" namespace OrthancStone -{ +{ class OrthancFrameLayerSource : public LayerSourceBase, - public IWebService::ICallback + public IWebService::ICallback // TODO move this into a PImpl { private: enum Content @@ -44,7 +45,15 @@ std::string instanceId_; unsigned int frame_; std::auto_ptr dataset_; + unsigned int frameWidth_; + unsigned int frameHeight_; Orthanc::PixelFormat format_; + double pixelSpacingX_; + double pixelSpacingY_; + IVolumeSlicesObserver* observer2_; + + protected: + virtual void StartInternal(); public: OrthancFrameLayerSource(IWebService& orthanc, @@ -53,6 +62,8 @@ virtual void SetObserver(IObserver& observer); + void SetObserver(IVolumeSlicesObserver& observer); + virtual void NotifyError(const std::string& uri, Orthanc::IDynamicObject* payload); diff -r 885932a893de -r 298f375dcb68 Framework/Toolbox/GeometryToolbox.h --- a/Framework/Toolbox/GeometryToolbox.h Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Toolbox/GeometryToolbox.h Wed May 17 22:03:09 2017 +0200 @@ -21,8 +21,9 @@ #pragma once -// Patch for Boost 1.64.0 +// Patch for ublas in Boost 1.64.0 // https://github.com/dealii/dealii/issues/4302 +#include #if BOOST_VERSION >= 106300 // or 64, need to check # include #endif diff -r 885932a893de -r 298f375dcb68 Framework/Toolbox/IVolumeSlicesObserver.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/IVolumeSlicesObserver.h Wed May 17 22:03:09 2017 +0200 @@ -0,0 +1,38 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public 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 "ParallelSlices.h" + +namespace OrthancStone +{ + class IVolumeSlicesObserver : public boost::noncopyable + { + public: + virtual ~IVolumeSlicesObserver() + { + } + + virtual void NotifySlicesAvailable(const ParallelSlices& slices) = 0; + }; +} + diff -r 885932a893de -r 298f375dcb68 Framework/Toolbox/SliceGeometry.cpp --- a/Framework/Toolbox/SliceGeometry.cpp Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Toolbox/SliceGeometry.cpp Wed May 17 22:03:09 2017 +0200 @@ -152,4 +152,14 @@ offsetX = boost::numeric::ublas::inner_prod(axisX_, projection - origin_); offsetY = boost::numeric::ublas::inner_prod(axisY_, projection - origin_); } + + + bool SliceGeometry::IsSamePlane(const SliceGeometry& other, + double sliceThickness) const + { + return (GeometryToolbox::IsParallel(GetNormal(), other.GetNormal()) && + GeometryToolbox::IsNear(ProjectAlongNormal(other.GetOrigin()), + ProjectAlongNormal(GetOrigin()), + sliceThickness / 2.0)); + } } diff -r 885932a893de -r 298f375dcb68 Framework/Toolbox/SliceGeometry.h --- a/Framework/Toolbox/SliceGeometry.h Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Toolbox/SliceGeometry.h Wed May 17 22:03:09 2017 +0200 @@ -88,5 +88,8 @@ void ProjectPoint(double& offsetX, double& offsetY, const Vector& point) const; + + bool IsSamePlane(const SliceGeometry& other, + double sliceThickness) const; }; } diff -r 885932a893de -r 298f375dcb68 Framework/Viewport/IViewport.h --- a/Framework/Viewport/IViewport.h Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Viewport/IViewport.h Wed May 17 22:03:09 2017 +0200 @@ -87,5 +87,7 @@ // Should only be called from IWidget virtual void NotifyChange(const IWidget& widget) = 0; + + virtual void Start() = 0; }; } diff -r 885932a893de -r 298f375dcb68 Framework/Viewport/WidgetViewport.cpp --- a/Framework/Viewport/WidgetViewport.cpp Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Viewport/WidgetViewport.cpp Wed May 17 22:03:09 2017 +0200 @@ -74,6 +74,7 @@ } backgroundChanged_ = true; + observers_.NotifyChange(this); return *widget; } @@ -264,4 +265,13 @@ centralWidget_->UpdateContent(); } } + + + void WidgetViewport::Start() + { + if (centralWidget_.get() != NULL) + { + centralWidget_->Start(); + } + } } diff -r 885932a893de -r 298f375dcb68 Framework/Viewport/WidgetViewport.h --- a/Framework/Viewport/WidgetViewport.h Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Viewport/WidgetViewport.h Wed May 17 22:03:09 2017 +0200 @@ -88,5 +88,7 @@ virtual bool HasUpdateContent(); virtual void UpdateContent(); + + virtual void Start(); }; } diff -r 885932a893de -r 298f375dcb68 Framework/Widgets/EmptyWidget.cpp --- a/Framework/Widgets/EmptyWidget.cpp Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Widgets/EmptyWidget.cpp Wed May 17 22:03:09 2017 +0200 @@ -26,16 +26,19 @@ namespace OrthancStone { - bool EmptyWidget::Render(Orthanc::ImageAccessor& surface) + namespace Samples { - // Note: This call is slow - Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255); - return true; - } + bool EmptyWidget::Render(Orthanc::ImageAccessor& surface) + { + // Note: This call is slow + Orthanc::ImageProcessing::Set(surface, red_, green_, blue_, 255); + return true; + } - void EmptyWidget::UpdateContent() - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + void EmptyWidget::UpdateContent() + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } } } diff -r 885932a893de -r 298f375dcb68 Framework/Widgets/EmptyWidget.h --- a/Framework/Widgets/EmptyWidget.h Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Widgets/EmptyWidget.h Wed May 17 22:03:09 2017 +0200 @@ -25,90 +25,97 @@ namespace OrthancStone { - /** - * This is a test widget that simply fills its surface with an - * uniform color. - **/ - class EmptyWidget : public IWidget + namespace Samples { - private: - uint8_t red_; - uint8_t green_; - uint8_t blue_; - - public: - EmptyWidget(uint8_t red, - uint8_t green, - uint8_t blue) : - red_(red), - green_(green), - blue_(blue) - { - } - - virtual void SetDefaultView() - { - } - - virtual void SetParent(OrthancStone::IWidget& widget) - { - } - - virtual void SetViewport(IViewport& viewport) - { - } - - virtual void NotifyChange() - { - } - - virtual void SetStatusBar(IStatusBar& statusBar) - { - } - - virtual void SetSize(unsigned int width, - unsigned int height) + /** + * This is a test widget that simply fills its surface with an + * uniform color. + **/ + class EmptyWidget : public IWidget { - } - - virtual bool Render(Orthanc::ImageAccessor& surface); + private: + uint8_t red_; + uint8_t green_; + uint8_t blue_; + + public: + EmptyWidget(uint8_t red, + uint8_t green, + uint8_t blue) : + red_(red), + green_(green), + blue_(blue) + { + } - virtual IMouseTracker* CreateMouseTracker(MouseButton button, - int x, - int y, - KeyboardModifiers modifiers) - { - return NULL; - } + virtual void SetDefaultView() + { + } + + virtual void SetParent(OrthancStone::IWidget& widget) + { + } + + virtual void SetViewport(IViewport& viewport) + { + } - virtual void RenderMouseOver(Orthanc::ImageAccessor& target, - int x, - int y) - { - } + virtual void NotifyChange() + { + } + + virtual void SetStatusBar(IStatusBar& statusBar) + { + } + + virtual void SetSize(unsigned int width, + unsigned int height) + { + } + + virtual bool Render(Orthanc::ImageAccessor& surface); - virtual void MouseWheel(MouseWheelDirection direction, - int x, - int y, - KeyboardModifiers modifiers) - { - } + virtual IMouseTracker* CreateMouseTracker(MouseButton button, + int x, + int y, + KeyboardModifiers modifiers) + { + return NULL; + } - virtual void KeyPressed(char key, - KeyboardModifiers modifiers) - { - } + virtual void RenderMouseOver(Orthanc::ImageAccessor& target, + int x, + int y) + { + } + + virtual void MouseWheel(MouseWheelDirection direction, + int x, + int y, + KeyboardModifiers modifiers) + { + } - virtual bool HasUpdateContent() const - { - return false; - } + virtual void KeyPressed(char key, + KeyboardModifiers modifiers) + { + } - virtual void UpdateContent(); + virtual bool HasUpdateContent() const + { + return false; + } + + virtual void UpdateContent(); - virtual bool HasRenderMouseOver() - { - return false; - } - }; + virtual bool HasRenderMouseOver() + { + return false; + } + + virtual void Start() + { + } + }; + } } diff -r 885932a893de -r 298f375dcb68 Framework/Widgets/IWidget.h --- a/Framework/Widgets/IWidget.h Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Widgets/IWidget.h Wed May 17 22:03:09 2017 +0200 @@ -74,5 +74,7 @@ // Subclasses can call this method to signal the display of the // widget must be refreshed virtual void NotifyChange() = 0; + + virtual void Start() = 0; }; } diff -r 885932a893de -r 298f375dcb68 Framework/Widgets/LayerWidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Widgets/LayerWidget.cpp Wed May 17 22:03:09 2017 +0200 @@ -0,0 +1,457 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "LayerWidget.h" + +#include "../../Resources/Orthanc/Core/Logging.h" + +namespace OrthancStone +{ + class LayerWidget::Scene : public boost::noncopyable + { + private: + SliceGeometry slice_; + size_t countMissing_; + std::vector renderers_; + + void DeleteLayer(size_t index) + { + if (index >= renderers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(countMissing_ <= renderers_.size()); + + if (renderers_[index] != NULL) + { + assert(countMissing_ < renderers_.size()); + delete renderers_[index]; + renderers_[index] = NULL; + countMissing_++; + } + } + + public: + Scene(const SliceGeometry& slice, + size_t countLayers) : + slice_(slice), + countMissing_(countLayers), + renderers_(countLayers, NULL) + { + } + + ~Scene() + { + for (size_t i = 0; i < renderers_.size(); i++) + { + DeleteLayer(i); + } + } + + void SetLayer(size_t index, + ILayerRenderer* renderer) // Takes ownership + { + if (renderer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + DeleteLayer(index); + + renderers_[index] = renderer; + countMissing_--; + } + + const SliceGeometry& GetSlice() const + { + return slice_; + } + + bool IsComplete() const + { + return countMissing_ == 0; + } + + bool IsSamePlane(const SliceGeometry& slice, + double sliceThickness) + { + return slice_.IsSamePlane(slice, sliceThickness); + } + + bool RenderScene(CairoContext& context, + const ViewportGeometry& view) + { + bool fullQuality = true; + + for (size_t i = 0; i < renderers_.size(); i++) + { + if (renderers_[i] != NULL && + !renderers_[i]->RenderLayer(context, view)) + { + return false; + } + + if (renderers_[i] != NULL && + !renderers_[i]->IsFullQuality()) + { + fullQuality = false; + } + } + + if (!fullQuality) + { + double x, y; + view.MapDisplayToScene(x, y, static_cast(view.GetDisplayWidth()) / 2.0, 10); + + cairo_t *cr = context.GetObject(); + cairo_translate(cr, x, y); + cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2 * M_PI); + cairo_set_line_width(cr, 2.0 / view.GetZoom()); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_stroke_preserve(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_fill(cr); + } + + return true; + } + + void SetLayerStyle(size_t index, + const RenderStyle& style) + { + if (renderers_[index] != NULL) + { + renderers_[index]->SetLayerStyle(style); + } + } + }; + + + bool LayerWidget::LookupLayer(size_t& index /* out */, + ILayerSource& layer) const + { + LayersIndex::const_iterator found = layersIndex_.find(&layer); + + if (found == layersIndex_.end()) + { + return false; + } + else + { + index = found->second; + assert(index < layers_.size() && + layers_[index] == &layer); + return true; + } + } + + + void LayerWidget::GetSceneExtent(double& x1, + double& y1, + double& x2, + double& y2) + { + bool first = true; + + for (size_t i = 0; i < layers_.size(); i++) + { + double ax, ay, bx, by; + + assert(layers_[i] != NULL); + if (layers_[i]->GetExtent(ax, ay, bx, by, slice_)) + { + if (ax > bx) + { + std::swap(ax, bx); + } + + if (ay > by) + { + std::swap(ay, by); + } + + //LOG(INFO) << "Extent of layer " << i << ": (" << ax << "," << ay << ")->(" << bx << "," << by << ")"; + printf("Extent %d: (%f,%f) -> (%f,%f)\n", (int) i, ax, ay, bx, by); + + if (first) + { + x1 = ax; + y1 = ay; + x2 = bx; + y2 = by; + first = false; + } + else + { + x1 = std::min(x1, ax); + y1 = std::min(y1, ay); + x2 = std::max(x2, bx); + y2 = std::max(y2, by); + } + } + } + + if (first) + { + // Set a default extent of (-1,-1) -> (0,0) + x1 = -1; + y1 = -1; + x2 = 1; + y2 = 1; + } + + // Ensure the extent is non-empty + if (x1 >= x2) + { + double tmp = x1; + x1 = tmp - 0.5; + x2 = tmp + 0.5; + } + + if (y1 >= y2) + { + double tmp = y1; + y1 = tmp - 0.5; + y2 = tmp + 0.5; + } + } + + + bool LayerWidget::RenderScene(CairoContext& context, + const ViewportGeometry& view) + { + if (currentScene_.get() != NULL) + { + return currentScene_->RenderScene(context, view); + } + else + { + return true; + } + } + + + void LayerWidget::ResetPendingScene() + { + pendingScene_.reset(new Scene(slice_, layers_.size())); + } + + + void LayerWidget::UpdateLayer(size_t index, + ILayerRenderer* renderer, + const SliceGeometry& slice) + { + printf("Updating layer %d\n", (int) index); + + std::auto_ptr tmp(renderer); + + if (renderer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (index >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(layers_.size() == styles_.size()); + renderer->SetLayerStyle(styles_[index]); + + if (currentScene_.get() != NULL && + currentScene_->IsSamePlane(slice, sliceThickness_)) + { + currentScene_->SetLayer(index, tmp.release()); + NotifyChange(); + } + else if (pendingScene_.get() != NULL && + pendingScene_->IsSamePlane(slice, sliceThickness_)) + { + pendingScene_->SetLayer(index, tmp.release()); + + if (currentScene_.get() == NULL || + pendingScene_->IsComplete()) + { + currentScene_ = pendingScene_; + NotifyChange(); + } + } + } + + + LayerWidget::LayerWidget() : + started_(false), + sliceThickness_(1) + { + SetBackgroundCleared(true); + } + + + LayerWidget::~LayerWidget() + { + for (size_t i = 0; i < layers_.size(); i++) + { + delete layers_[i]; + } + } + + + size_t LayerWidget::AddLayer(ILayerSource* layer) // Takes ownership + { + if (layer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + size_t index = layers_.size(); + layers_.push_back(layer); + styles_.push_back(RenderStyle()); + layersIndex_[layer] = index; + + ResetPendingScene(); + layer->SetObserver(*this); + + return index; + } + + + void LayerWidget::SetLayerStyle(size_t layer, + const RenderStyle& style) + { + if (layer >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(layers_.size() == styles_.size()); + styles_[layer] = style; + + if (currentScene_.get() != NULL) + { + currentScene_->SetLayerStyle(layer, style); + } + + if (pendingScene_.get() != NULL) + { + pendingScene_->SetLayerStyle(layer, style); + } + + NotifyChange(); + } + + + void LayerWidget::SetSlice(const SliceGeometry& slice, + double sliceThickness) + { + if (!slice_.IsSamePlane(slice, 100.0 * std::numeric_limits::epsilon())) + { + if (currentScene_.get() == NULL || + (pendingScene_.get() != NULL && + pendingScene_->IsComplete())) + { + currentScene_ = pendingScene_; + } + + slice_ = slice; + sliceThickness_ = sliceThickness; + ResetPendingScene(); + + if (started_) + { + for (size_t i = 0; i < layers_.size(); i++) + { + assert(layers_[i] != NULL); + layers_[i]->ScheduleLayerCreation(slice_); + } + } + } + } + + + void LayerWidget::NotifyGeometryReady(ILayerSource& source) + { + size_t i; + if (LookupLayer(i, source)) + printf("Geometry ready for layer %d\n", (int) i); + + SetDefaultView(); + layers_[i]->ScheduleLayerCreation(slice_); + } + + + void LayerWidget::NotifySourceChange(ILayerSource& source) + { + source.ScheduleLayerCreation(slice_); + } + + + void LayerWidget::NotifySliceChange(ILayerSource& source, + const SliceGeometry& slice) + { + if (slice_.IsSamePlane(slice, sliceThickness_)) + { + source.ScheduleLayerCreation(slice_); + } + } + + + void LayerWidget::NotifyLayerReady(ILayerRenderer* renderer, + ILayerSource& source, + const SliceGeometry& viewportSlice) + { + std::auto_ptr tmp(renderer); + + size_t i; + if (LookupLayer(i, source)) + printf("Renderer ready for layer %d\n", (int) i); + + size_t index; + if (LookupLayer(index, source)) + { + UpdateLayer(index, tmp.release(), viewportSlice); + } + } + + + void LayerWidget::NotifyLayerError(ILayerSource& source, + const SliceGeometry& viewportSlice) + { + size_t i; + if (LookupLayer(i, source)) + LOG(ERROR) << "Error on layer " << i; + } + + + void LayerWidget::Start() + { + if (started_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + for (size_t i = 0; i < layers_.size(); i++) + { + assert(layers_[i] != NULL); + layers_[i]->Start(); + } + } +} diff -r 885932a893de -r 298f375dcb68 Framework/Widgets/LayerWidget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Widgets/LayerWidget.h Wed May 17 22:03:09 2017 +0200 @@ -0,0 +1,98 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public 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 "WorldSceneWidget.h" +#include "../Layers/ILayerSource.h" + +#include + +namespace OrthancStone +{ + class LayerWidget : + public WorldSceneWidget, + public ILayerSource::IObserver // TODO move this as PImpl + { + private: + class Scene; + + typedef std::map LayersIndex; + + bool started_; + LayersIndex layersIndex_; + std::vector layers_; + std::vector styles_; + SliceGeometry slice_; + double sliceThickness_; + std::auto_ptr currentScene_; + std::auto_ptr pendingScene_; + + + bool LookupLayer(size_t& index /* out */, + ILayerSource& layer) const; + + + protected: + virtual void GetSceneExtent(double& x1, + double& y1, + double& x2, + double& y2); + + virtual bool RenderScene(CairoContext& context, + const ViewportGeometry& view); + + void ResetPendingScene(); + + void UpdateLayer(size_t index, + ILayerRenderer* renderer, + const SliceGeometry& slice); + + public: + LayerWidget(); + + virtual ~LayerWidget(); + + size_t AddLayer(ILayerSource* layer); // Takes ownership + + void SetLayerStyle(size_t layer, + const RenderStyle& style); + + void SetSlice(const SliceGeometry& slice, + double sliceThickness); + + virtual void NotifyGeometryReady(ILayerSource& source); + + virtual void NotifySourceChange(ILayerSource& source); + + virtual void NotifySliceChange(ILayerSource& source, + const SliceGeometry& slice); + + virtual void NotifyLayerReady(ILayerRenderer* renderer, + ILayerSource& source, + const SliceGeometry& viewportSlice); + + virtual void NotifyLayerError(ILayerSource& source, + const SliceGeometry& viewportSlice); + + virtual void Start(); + }; +} diff -r 885932a893de -r 298f375dcb68 Framework/Widgets/WidgetBase.h --- a/Framework/Widgets/WidgetBase.h Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Widgets/WidgetBase.h Wed May 17 22:03:09 2017 +0200 @@ -99,5 +99,9 @@ } virtual void NotifyChange(); + + virtual void Start() + { + } }; } diff -r 885932a893de -r 298f375dcb68 Framework/Widgets/WorldSceneWidget.cpp --- a/Framework/Widgets/WorldSceneWidget.cpp Tue May 16 22:12:41 2017 +0200 +++ b/Framework/Widgets/WorldSceneWidget.cpp Wed May 17 22:03:09 2017 +0200 @@ -268,8 +268,8 @@ if (observers_.IsEmpty()) { - // Without a size observer, use the default view - view_.SetDefaultView(); + // Without a size observer, reset to the default view + // view_.SetDefaultView(); } else { diff -r 885932a893de -r 298f375dcb68 Resources/CMake/OrthancStone.cmake --- a/Resources/CMake/OrthancStone.cmake Tue May 16 22:12:41 2017 +0200 +++ b/Resources/CMake/OrthancStone.cmake Wed May 17 22:03:09 2017 +0200 @@ -222,6 +222,7 @@ ${ORTHANC_STONE_DIR}/Framework/Widgets/TestCairoWidget.cpp ${ORTHANC_STONE_DIR}/Framework/Widgets/TestWorldSceneWidget.cpp ${ORTHANC_STONE_DIR}/Framework/Widgets/WidgetBase.cpp + ${ORTHANC_STONE_DIR}/Framework/Widgets/LayerWidget.cpp ${ORTHANC_STONE_DIR}/Framework/Widgets/WorldSceneWidget.cpp ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp diff -r 885932a893de -r 298f375dcb68 UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Tue May 16 22:12:41 2017 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed May 17 22:03:09 2017 +0200 @@ -24,6 +24,7 @@ #include "../Resources/Orthanc/Core/Logging.h" #include "../Framework/Toolbox/OrthancWebService.h" #include "../Framework/Layers/OrthancFrameLayerSource.h" +#include "../Framework/Widgets/LayerWidget.h" namespace OrthancStone @@ -60,6 +61,97 @@ { } }; + + + + /*class OrthancInstanceLoader : public IWebService::ICallback + { + public: + class ICallback : public boost::noncopyable + { + public: + virtual ~ICallback() + { + } + + virtual void NotifyInstanceLoaded(const std::string& instanceId, + const OrthancPlugins::FullOrthancDataset& dicom) = 0; + + virtual void NotifyInstanceError(const std::string& instanceId) = 0; + }; + + private: + class Operation : public Orthanc::IDynamicObject + { + private: + ICallback& callback_; + std::string instanceId_; + + public: + Operation(ICallback& callback, + const std::string& instanceId) : + callback_(callback), + instanceId_(instanceId) + { + } + + ICallback& GetCallback() + { + return callback_; + } + + const std::string& GetInstanceId() const + { + return instanceId_; + } + }; + + IWebService& orthanc_; + + public: + OrthancInstanceLoader(IWebService& orthanc) : + orthanc_(orthanc) + { + } + + void ScheduleLoadInstance(ICallback& callback, + const std::string& instanceId) + { + orthanc_.ScheduleGetRequest(*this, + "/instances/" + instanceId + "/tags", + new Operation(callback, instanceId)); + } + + void NotifySuccess(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) + { + std::auto_ptr operation(reinterpret_cast(payload)); + + try + { + OrthancPlugins::FullOrthancDataset dataset(answer, answerSize); + operation->GetCallback().NotifyInstanceLoaded(operation->GetInstanceId(), dataset); + } + catch (Orthanc::OrthancException&) + { + operation->GetCallback().NotifyInstanceError(operation->GetInstanceId()); + } + } + + void NotifyError(const std::string& uri, + Orthanc::IDynamicObject* payload) + { + std::auto_ptr operation(reinterpret_cast(payload)); + + LOG(ERROR) << "Cannot download " << uri; + operation->GetCallback().NotifyInstanceError(operation->GetInstanceId()); + } + };*/ + + + } @@ -75,6 +167,10 @@ OrthancStone::SliceGeometry slice; source.ScheduleLayerCreation(slice); + + + OrthancStone::LayerWidget widget; + printf(">> %d\n", widget.AddLayer(new OrthancStone::OrthancFrameLayerSource(orthanc, "befb52a6-b4b04954-b5a019c3-fdada9d7-dddc9430", 0))); }