Mercurial > hg > orthanc-stone
changeset 1586:b5417e377636
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 22 Oct 2020 16:17:10 +0200 |
parents | 94edbfa64c97 |
children | a1405ab3a91c |
files | Applications/Resources/Colormaps/GenerateColormaps.py Applications/Resources/Colormaps/blue.lut Applications/Resources/Colormaps/green.lut Applications/Resources/Colormaps/hot.lut Applications/Resources/Colormaps/jet.lut Applications/Resources/Colormaps/red.lut Applications/Resources/Graveyard/Messaging/CurlOrthancConnection.cpp Applications/Resources/Graveyard/Messaging/CurlOrthancConnection.h Applications/Resources/Graveyard/Messaging/IOrthancConnection.h Applications/Resources/Graveyard/ReferenceLineFactory.cpp Applications/Resources/Graveyard/ReferenceLineFactory.h Applications/Resources/Graveyard/Threading/BinarySemaphore.cpp Applications/Resources/Graveyard/Threading/BinarySemaphore.h Applications/Resources/Graveyard/Threading/IThreadSafety.h Applications/Resources/Graveyard/Threading/SdlBuffering.cpp Applications/Resources/Graveyard/Threading/SdlBuffering.h Applications/Resources/Graveyard/Threading/SharedValue.h Applications/Resources/Graveyard/Toolbox/DicomDataset.cpp Applications/Resources/Graveyard/Toolbox/DicomDataset.h Applications/Resources/LinuxStandardBaseUic.py Applications/Samples/Sdl/CMakeLists.txt Applications/Samples/WebAssembly/CMakeLists.txt OrthancStone/Docs/Conventions.txt OrthancStone/Docs/stone-object-model-reference.md OrthancStone/Resources/CMake/LinuxStandardBaseUic.py OrthancStone/Resources/Colormaps/GenerateColormaps.py OrthancStone/Resources/Colormaps/blue.lut OrthancStone/Resources/Colormaps/green.lut OrthancStone/Resources/Colormaps/hot.lut OrthancStone/Resources/Colormaps/jet.lut OrthancStone/Resources/Colormaps/red.lut OrthancStone/Resources/Documentation/Conventions.txt OrthancStone/Resources/Documentation/stone-object-model-reference.md OrthancStone/Resources/Graveyard/Messaging/CurlOrthancConnection.cpp OrthancStone/Resources/Graveyard/Messaging/CurlOrthancConnection.h OrthancStone/Resources/Graveyard/Messaging/IOrthancConnection.h OrthancStone/Resources/Graveyard/ReferenceLineFactory.cpp OrthancStone/Resources/Graveyard/ReferenceLineFactory.h OrthancStone/Resources/Graveyard/Threading/BinarySemaphore.cpp OrthancStone/Resources/Graveyard/Threading/BinarySemaphore.h OrthancStone/Resources/Graveyard/Threading/IThreadSafety.h OrthancStone/Resources/Graveyard/Threading/SdlBuffering.cpp OrthancStone/Resources/Graveyard/Threading/SdlBuffering.h OrthancStone/Resources/Graveyard/Threading/SharedValue.h OrthancStone/Resources/Graveyard/Toolbox/DicomDataset.cpp OrthancStone/Resources/Graveyard/Toolbox/DicomDataset.h |
diffstat | 46 files changed, 1790 insertions(+), 1790 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Colormaps/GenerateColormaps.py Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,36 @@ +#!/usr/bin/python + +import array +import matplotlib.pyplot as plt + +def GenerateColormap(name): + colormap = [] + + for gray in range(256): + if name == 'red': + color = (gray / 255.0, 0, 0) + elif name == 'green': + color = (0, gray / 255.0, 0) + elif name == 'blue': + color = (0, 0, gray / 255.0) + else: + color = plt.get_cmap(name) (gray) + + colormap += map(lambda k: int(round(color[k] * 255)), range(3)) + + colormap[0] = 0 + colormap[1] = 0 + colormap[2] = 0 + + return array.array('B', colormap).tostring() + + +for name in [ + 'hot', + 'jet', + 'blue', + 'green', + 'red', +]: + with open('%s.lut' % name, 'w') as f: + f.write(GenerateColormap(name))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Messaging/CurlOrthancConnection.cpp Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,72 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "CurlOrthancConnection.h" + +#if ORTHANC_ENABLE_CURL == 1 + +#include "../../Resources/Orthanc/Core/HttpClient.h" +#include "../../Resources/Orthanc/Core/OrthancException.h" + +namespace OrthancStone +{ + void CurlOrthancConnection::RestApiGet(std::string& result, + const std::string& uri) + { + /** + * TODO: This function sometimes crashes if compiled with + * MinGW-W64 (32 bit) in Release mode, on Windows XP. Introducing + * a mutex here fixes the issue. Not sure of what is the + * culprit. Maybe a bug in a old version of MinGW? + **/ + + Orthanc::HttpClient client(parameters_, uri); + + // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc) + client.SetRedirectionFollowed(false); + + if (!client.Apply(result)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + } + + + void CurlOrthancConnection::RestApiPost(std::string& result, + const std::string& uri, + const std::string& body) + { + Orthanc::HttpClient client(parameters_, uri); + + // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc) + client.SetRedirectionFollowed(false); + + client.SetBody(body); + client.SetMethod(Orthanc::HttpMethod_Post); + + if (!client.Apply(result)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + } +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Messaging/CurlOrthancConnection.h Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,57 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IOrthancConnection.h" + +#if ORTHANC_ENABLE_CURL == 1 + +#include "../../Resources/Orthanc/Core/WebServiceParameters.h" + +namespace OrthancStone +{ + class CurlOrthancConnection : public IOrthancConnection + { + private: + Orthanc::WebServiceParameters parameters_; + + public: + CurlOrthancConnection(const Orthanc::WebServiceParameters& parameters) : + parameters_(parameters) + { + } + + const Orthanc::WebServiceParameters& GetParameters() const + { + return parameters_; + } + + virtual void RestApiGet(std::string& result, + const std::string& uri); + + virtual void RestApiPost(std::string& result, + const std::string& uri, + const std::string& body); + }; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Messaging/IOrthancConnection.h Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,41 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Toolbox/IThreadSafety.h" + +#include <string> + +namespace OrthancStone +{ + // Derived classes must be thread-safe + class IOrthancConnection : public IThreadSafe + { + public: + virtual void RestApiGet(std::string& result, + const std::string& uri) = 0; + + virtual void RestApiPost(std::string& result, + const std::string& uri, + const std::string& body) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/ReferenceLineFactory.cpp Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,137 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "ReferenceLineFactory.h" + +#include "LineLayerRenderer.h" + +namespace OrthancStone +{ + ReferenceLineFactory::ReferenceLineFactory(SliceViewerWidget& owner, + SliceViewerWidget& sibling) : + owner_(owner), + sibling_(sibling), + hasLayerIndex_(false) + { + style_.SetColor(0, 255, 0); + slice_ = sibling.GetSlice(); + sibling_.Register(*this); + } + + + void ReferenceLineFactory::NotifySliceContentChange(const SliceViewerWidget& source, + const SliceGeometry& slice) + { + if (&source == &sibling_) + { + SetSlice(slice); + } + } + + + void ReferenceLineFactory::SetLayerIndex(size_t layerIndex) + { + hasLayerIndex_ = true; + layerIndex_ = layerIndex; + } + + + void ReferenceLineFactory::SetStyle(const RenderStyle& style) + { + style_ = style; + } + + + RenderStyle ReferenceLineFactory::GetRenderStyle() + { + return style_; + } + + + void ReferenceLineFactory::SetSlice(const SliceGeometry& slice) + { + slice_ = slice; + + if (hasLayerIndex_) + { + owner_.InvalidateLayer(layerIndex_); + } + } + + + ILayerRenderer* ReferenceLineFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice) + { + Vector p, d; + + // Compute the line of intersection between the two slices + if (!GeometryToolbox::IntersectTwoPlanes(p, d, + slice_.GetOrigin(), slice_.GetNormal(), + viewportSlice.GetOrigin(), viewportSlice.GetNormal())) + { + // The two slice are parallel, don't try and display the intersection + return NULL; + } + + double x1, y1, x2, y2; + viewportSlice.ProjectPoint(x1, y1, p); + viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d); + + double sx1, sy1, sx2, sy2; + owner_.GetView().GetSceneExtent(sx1, sy1, sx2, sy2); + + if (GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, + x1, y1, x2, y2, + sx1, sy1, sx2, sy2)) + { + std::unique_ptr<ILayerRenderer> layer(new LineLayerRenderer(x1, y1, x2, y2)); + layer->SetLayerStyle(style_); + return layer.release(); + } + else + { + // Parallel slices + return NULL; + } + } + + + ISliceableVolume& ReferenceLineFactory::GetSourceVolume() const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + + void ReferenceLineFactory::Configure(SliceViewerWidget& a, + SliceViewerWidget& b) + { + { + size_t layerIndex; + ILayerRendererFactory& factory = a.AddLayer(layerIndex, new ReferenceLineFactory(a, b)); + dynamic_cast<ReferenceLineFactory&>(factory).SetLayerIndex(layerIndex); + } + + { + size_t layerIndex; + ILayerRendererFactory& factory = b.AddLayer(layerIndex, new ReferenceLineFactory(b, a)); + dynamic_cast<ReferenceLineFactory&>(factory).SetLayerIndex(layerIndex); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/ReferenceLineFactory.h Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,77 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Widgets/SliceViewerWidget.h" + +namespace OrthancStone +{ + class ReferenceLineFactory : + public ILayerRendererFactory, + public SliceViewerWidget::ISliceObserver + { + private: + SliceViewerWidget& owner_; + SliceViewerWidget& sibling_; + SliceGeometry slice_; + RenderStyle style_; + bool hasLayerIndex_; + size_t layerIndex_; + + + public: + ReferenceLineFactory(SliceViewerWidget& owner, + SliceViewerWidget& sibling); + + virtual void NotifySliceContentChange(const SliceViewerWidget& source, + const SliceGeometry& slice); + + void SetLayerIndex(size_t layerIndex); + + void SetStyle(const RenderStyle& style); + + RenderStyle GetRenderStyle(); + + void SetSlice(const SliceGeometry& slice); + + virtual bool GetExtent(double& x1, + double& y1, + double& x2, + double& y2, + const SliceGeometry& viewportSlice) + { + return false; + } + + virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice); + + virtual bool HasSourceVolume() const + { + return false; + } + + virtual ISliceableVolume& GetSourceVolume() const; + + static void Configure(SliceViewerWidget& a, + SliceViewerWidget& b); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Threading/BinarySemaphore.cpp Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,50 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "BinarySemaphore.h" + +namespace OrthancStone +{ + BinarySemaphore::BinarySemaphore() : + proceed_(false) + { + } + + void BinarySemaphore::Signal() + { + //boost::mutex::scoped_lock lock(mutex_); + + proceed_ = true; + condition_.notify_one(); + } + + void BinarySemaphore::Wait() + { + boost::mutex::scoped_lock lock(mutex_); + + while (!proceed_) + { + condition_.wait(lock); + } + + proceed_ = false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Threading/BinarySemaphore.h Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,43 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition.hpp> + +namespace OrthancStone +{ + class BinarySemaphore : public boost::noncopyable + { + private: + bool proceed_; + boost::mutex mutex_; + boost::condition_variable condition_; + + public: + explicit BinarySemaphore(); + + void Signal(); + + void Wait(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Threading/IThreadSafety.h Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,57 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/noncopyable.hpp> + +namespace OrthancStone +{ + /** + * Dummy interface to explicitely tag the interfaces whose derived + * class must be thread-safe. The different methods of such classes + * might be simlultaneously invoked by several threads, and should + * be properly protected by mutexes. + **/ + class IThreadSafe : public boost::noncopyable + { + public: + virtual ~IThreadSafe() + { + } + }; + + + /** + * Dummy interface to explicitely tag the interfaces that are NOT + * expected to be thread-safe. The Orthanc Stone framework ensures + * that at most one method of such classes will be invoked at a + * given time. Such classes are automatically protected by the + * Orthanc Stone framework wherever required. + **/ + class IThreadUnsafe : public boost::noncopyable + { + public: + virtual ~IThreadUnsafe() + { + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Threading/SdlBuffering.cpp Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,134 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "SdlBuffering.h" + +#if ORTHANC_ENABLE_SDL == 1 + +#include "../../Resources/Orthanc/Core/Logging.h" +#include "../../Resources/Orthanc/Core/OrthancException.h" + +namespace OrthancStone +{ + SdlBuffering::SdlBuffering() : + sdlSurface_(NULL), + pendingFrame_(false) + { + } + + + SdlBuffering::~SdlBuffering() + { + if (sdlSurface_) + { + SDL_FreeSurface(sdlSurface_); + } + } + + + void SdlBuffering::SetSize(unsigned int width, + unsigned int height, + IViewport& viewport) + { + boost::mutex::scoped_lock lock(mutex_); + + viewport.SetSize(width, height); + + if (offscreenSurface_.get() == NULL || + offscreenSurface_->GetWidth() != width || + offscreenSurface_->GetHeight() != height) + { + offscreenSurface_.reset(new CairoSurface(width, height)); + } + + if (onscreenSurface_.get() == NULL || + onscreenSurface_->GetWidth() != width || + onscreenSurface_->GetHeight() != height) + { + onscreenSurface_.reset(new CairoSurface(width, height)); + + // TODO Big endian? + static const uint32_t rmask = 0x00ff0000; + static const uint32_t gmask = 0x0000ff00; + static const uint32_t bmask = 0x000000ff; + + if (sdlSurface_) + { + SDL_FreeSurface(sdlSurface_); + } + + sdlSurface_ = SDL_CreateRGBSurfaceFrom(onscreenSurface_->GetBuffer(), width, height, 32, + onscreenSurface_->GetPitch(), rmask, gmask, bmask, 0); + if (!sdlSurface_) + { + LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + pendingFrame_ = false; + } + + + bool SdlBuffering::RenderOffscreen(IViewport& viewport) + { + boost::mutex::scoped_lock lock(mutex_); + + if (offscreenSurface_.get() == NULL) + { + return false; + } + + Orthanc::ImageAccessor target = offscreenSurface_->GetAccessor(); + + if (viewport.Render(target) && + !pendingFrame_) + { + pendingFrame_ = true; + return true; + } + else + { + return false; + } + } + + + void SdlBuffering::SwapToScreen(SdlWindow& window) + { + if (!pendingFrame_ || + offscreenSurface_.get() == NULL || + onscreenSurface_.get() == NULL) + { + return; + } + + { + boost::mutex::scoped_lock lock(mutex_); + onscreenSurface_->Copy(*offscreenSurface_); + } + + window.Render(sdlSurface_); + pendingFrame_ = false; + } +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Threading/SdlBuffering.h Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,60 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if ORTHANC_ENABLE_SDL == 1 + +#include "SdlWindow.h" +#include "../../Framework/Viewport/CairoSurface.h" +#include "../../Framework/Viewport/IViewport.h" + +#include <boost/thread/mutex.hpp> + +namespace OrthancStone +{ + class SdlBuffering : public boost::noncopyable + { + private: + boost::mutex mutex_; + std::unique_ptr<CairoSurface> offscreenSurface_; + std::unique_ptr<CairoSurface> onscreenSurface_; + SDL_Surface* sdlSurface_; + bool pendingFrame_; + + public: + SdlBuffering(); + + ~SdlBuffering(); + + void SetSize(unsigned int width, + unsigned int height, + IViewport& viewport); + + // Returns "true" if a new refresh of the display should be + // triggered afterwards + bool RenderOffscreen(IViewport& viewport); + + void SwapToScreen(SdlWindow& window); + }; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Threading/SharedValue.h Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,58 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/noncopyable.hpp> +#include <boost/thread/mutex.hpp> + +namespace OrthancStone +{ + // A value that is protected by a mutex, in order to be shared by + // multiple threads + template <typename T> + class SharedValue : public boost::noncopyable + { + private: + boost::mutex mutex_; + T value_; + + public: + class Locker : public boost::noncopyable + { + private: + boost::mutex::scoped_lock lock_; + T& value_; + + public: + Locker(SharedValue& shared) : + lock_(shared.mutex_), + value_(shared.value_) + { + } + + T& GetValue() const + { + return value_; + } + }; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Toolbox/DicomDataset.cpp Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,304 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "DicomDataset.h" + +#include "../../Resources/Orthanc/Core/OrthancException.h" +#include "../../Resources/Orthanc/Core/Logging.h" +#include "../../Resources/Orthanc/Core/Toolbox.h" + +#include <boost/lexical_cast.hpp> +#include <json/value.h> +#include <json/reader.h> + +namespace OrthancStone +{ + static uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + + static uint16_t GetHexadecimalValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + + static DicomDataset::Tag ParseTag(const std::string& tag) + { + if (tag.size() == 9 && + isxdigit(tag[0]) && + isxdigit(tag[1]) && + isxdigit(tag[2]) && + isxdigit(tag[3]) && + (tag[4] == '-' || tag[4] == ',') && + isxdigit(tag[5]) && + isxdigit(tag[6]) && + isxdigit(tag[7]) && + isxdigit(tag[8])) + { + uint16_t group = GetHexadecimalValue(tag.c_str()); + uint16_t element = GetHexadecimalValue(tag.c_str() + 5); + return std::make_pair(group, element); + } + else if (tag.size() == 8 && + isxdigit(tag[0]) && + isxdigit(tag[1]) && + isxdigit(tag[2]) && + isxdigit(tag[3]) && + isxdigit(tag[4]) && + isxdigit(tag[5]) && + isxdigit(tag[6]) && + isxdigit(tag[7])) + { + uint16_t group = GetHexadecimalValue(tag.c_str()); + uint16_t element = GetHexadecimalValue(tag.c_str() + 4); + return std::make_pair(group, element); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + void DicomDataset::Parse(const std::string& content) + { + Json::Value json; + Json::Reader reader; + if (!reader.parse(content, json)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + Parse(json); + } + + + void DicomDataset::Parse(const Json::Value& content) + { + if (content.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + Json::Value::Members members = content.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + Tag tag = ParseTag(members[i]); + + const Json::Value& item = content[members[i]]; + + if (item.type() != Json::objectValue || + !item.isMember("Type") || + !item.isMember("Value") || + !item.isMember("Name") || + item["Type"].type() != Json::stringValue || + item["Name"].type() != Json::stringValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (item["Type"].asString() == "String") + { + if (item["Value"].type() == Json::stringValue) + { + values_[tag] = item["Value"].asString(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + } + } + + + DicomDataset::DicomDataset(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instanceId) + { + std::string content; + orthanc.RestApiGet(content, "/instances/" + instanceId + "/tags"); + + Parse(content); + } + + + std::string DicomDataset::GetStringValue(const Tag& tag) const + { + Values::const_iterator it = values_.find(tag); + + if (it == values_.end()) + { + LOG(ERROR) << "Trying to access a DICOM tag that is not set in a DICOM dataset"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); + } + else + { + return it->second; + } + } + + + std::string DicomDataset::GetStringValue(const Tag& tag, + const std::string& defaultValue) const + { + Values::const_iterator it = values_.find(tag); + + if (it == values_.end()) + { + return defaultValue; + } + else + { + return it->second; + } + } + + + float DicomDataset::GetFloatValue(const Tag& tag) const + { + try + { + return boost::lexical_cast<float>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag))); + } + catch (boost::bad_lexical_cast&) + { + LOG(ERROR) << "Trying to access a DICOM tag that is not a float"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + double DicomDataset::GetDoubleValue(const Tag& tag) const + { + try + { + return boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag))); + } + catch (boost::bad_lexical_cast&) + { + LOG(ERROR) << "Trying to access a DICOM tag that is not a float"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + int DicomDataset::GetIntegerValue(const Tag& tag) const + { + try + { + return boost::lexical_cast<int>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag))); + } + catch (boost::bad_lexical_cast&) + { + LOG(ERROR) << "Trying to access a DICOM tag that is not an integer"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + unsigned int DicomDataset::GetUnsignedIntegerValue(const Tag& tag) const + { + int v = GetIntegerValue(tag); + + if (v < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + return static_cast<unsigned int>(v); + } + } + + + void DicomDataset::GetVectorValue(Vector& vector, + const Tag& tag) const + { + if (!GeometryToolbox::ParseVector(vector, Orthanc::Toolbox::StripSpaces(GetStringValue(tag)))) + { + LOG(ERROR) << "Trying to access a DICOM tag that is not a vector"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + void DicomDataset::GetVectorValue(Vector& vector, + const Tag& tag, + size_t expectedSize) const + { + GetVectorValue(vector, tag); + + if (vector.size() != expectedSize) + { + LOG(ERROR) << "A vector in a DICOM tag has a bad size"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + void DicomDataset::Print() const + { + for (Values::const_iterator it = values_.begin(); it != values_.end(); ++it) + { + printf("%04x,%04x = [%s]\n", it->first.first, it->first.second, it->second.c_str()); + } + printf("\n"); + } + + + bool DicomDataset::IsGrayscale() const + { + std::string photometric = Orthanc::Toolbox::StripSpaces(GetStringValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION)); + + return (photometric == "MONOCHROME1" || + photometric == "MONOCHROME2"); + } + + + void DicomDataset::GetPixelSpacing(double& spacingX, + double& spacingY) const + { + if (HasTag(DICOM_TAG_PIXEL_SPACING)) + { + Vector spacing; + GetVectorValue(spacing, DICOM_TAG_PIXEL_SPACING, 2); + spacingX = spacing[0]; + spacingY = spacing[1]; + } + else + { + spacingX = 1.0; + spacingY = 1.0; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Graveyard/Toolbox/DicomDataset.h Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,109 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h" + +#include <map> +#include <stdint.h> +#include <json/value.h> + +namespace OrthancStone +{ + // This class is NOT thread-safe + // This is a lightweight alternative to Orthanc::DicomMap + class DicomDataset : public boost::noncopyable + { + public: + typedef std::pair<uint16_t, uint16_t> Tag; + + private: + typedef std::map<Tag, std::string> Values; + + Values values_; + + void Parse(const std::string& content); + + void Parse(const Json::Value& content); + + public: + DicomDataset(const std::string& content) + { + Parse(content); + } + + DicomDataset(const Json::Value& content) + { + Parse(content); + } + + DicomDataset(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instanceId); + + bool HasTag(const Tag& tag) const + { + return values_.find(tag) != values_.end(); + } + + std::string GetStringValue(const Tag& tag) const; + + std::string GetStringValue(const Tag& tag, + const std::string& defaultValue) const; + + float GetFloatValue(const Tag& tag) const; + + double GetDoubleValue(const Tag& tag) const; + + int GetIntegerValue(const Tag& tag) const; + + unsigned int GetUnsignedIntegerValue(const Tag& tag) const; + + void GetVectorValue(Vector& vector, + const Tag& tag, + size_t expectedSize) const; + + void GetVectorValue(Vector& vector, + const Tag& tag) const; + + void Print() const; + + bool IsGrayscale() const; + + void GetPixelSpacing(double& spacingX, + double& spacingY) const; + }; + + + static const DicomDataset::Tag DICOM_TAG_COLUMNS(0x0028, 0x0011); + static const DicomDataset::Tag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037); + static const DicomDataset::Tag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032); + static const DicomDataset::Tag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008); + static const DicomDataset::Tag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103); + static const DicomDataset::Tag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030); + static const DicomDataset::Tag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052); + static const DicomDataset::Tag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053); + static const DicomDataset::Tag DICOM_TAG_ROWS(0x0028, 0x0010); + static const DicomDataset::Tag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050); + static const DicomDataset::Tag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050); + static const DicomDataset::Tag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051); + static const DicomDataset::Tag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/LinuxStandardBaseUic.py Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +import subprocess +import sys + +if len(sys.argv) <= 1: + sys.stderr.write('Please provide arguments for uic\n') + sys.exit(-1) + +path = '' +pos = 1 +while pos < len(sys.argv): + if sys.argv[pos].startswith('-'): + pos += 2 + else: + path = sys.argv[pos] + break + +if len(path) == 0: + sys.stderr.write('Unable to find the input file in the arguments to uic\n') + sys.exit(-1) + +with open(path, 'r') as f: + lines = f.read().split('\n') + if (len(lines) > 1 and + lines[0].startswith('<?')): + content = '\n'.join(lines[1:]) + else: + content = '\n'.join(lines) + +# Remove the source file from the arguments +args = sys.argv[1:pos] + sys.argv[pos+1:] + +p = subprocess.Popen([ '/opt/lsb/bin/uic' ] + args, + stdin = subprocess.PIPE) +p.communicate(input = content)
--- a/Applications/Samples/Sdl/CMakeLists.txt Wed Oct 21 18:00:34 2020 +0200 +++ b/Applications/Samples/Sdl/CMakeLists.txt Thu Oct 22 16:17:10 2020 +0200 @@ -40,7 +40,7 @@ "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") EmbedResources( - COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut + COLORMAP_HOT ${CMAKE_SOURCE_DIR}/../../Resources/Colormaps/hot.lut UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf )
--- a/Applications/Samples/WebAssembly/CMakeLists.txt Wed Oct 21 18:00:34 2020 +0200 +++ b/Applications/Samples/WebAssembly/CMakeLists.txt Thu Oct 22 16:17:10 2020 +0200 @@ -52,7 +52,7 @@ "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") EmbedResources( - COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut + COLORMAP_HOT ${CMAKE_SOURCE_DIR}/../../Resources/Colormaps/hot.lut UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf )
--- a/OrthancStone/Docs/Conventions.txt Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ - -Some notes about the lifetime of objects -======================================== - -Stone applications ------------------- - -A typical Stone application can be split in 3 parts: - -1- The "loaders part" and the associated "IOracle", that communicate - through "IMessage" objects. The lifetime of these objects is - governed by the "IStoneContext". - -2- The "data part" holds the data loaded by the "loaders part". The - related objects must not be aware of the oracle, neither of the - messages. It is up to the user application to store these objects. - -3- The "viewport part" is based upon the "Scene2D" class. - - -Multithreading --------------- - -* Stone makes the hypothesis that its objects live in a single thread. - All the content of the "Framework" folder (with the exception of - the "Oracle" stuff) must not use "boost::thread". - -* The "IOracleCommand" classes represent commands that must be - executed asynchronously from the Stone thread. Their actual - execution is done by the "IOracle". - -* In WebAssembly, the "IOracle" corresponds to the "html5.h" - facilities (notably for the Fetch API). There is no mutex here, as - JavaScript is inherently single-threaded. - -* In plain C++ applications, the "IOracle" corresponds to a FIFO queue - of commands that are executed by a pool of threads. The Stone - context holds a global mutex, that must be properly locked by the - user application, and by the "IOracle" when it sends back messages - to the Stone loaders (cf. class "IMessageEmitter"). - -* Multithreading is thus achieved by defining new oracle commands by - subclassing "IOracleCommand", then by defining a way to execute them - (cf. class "GenericCommandRunner"). - - -References between objects --------------------------- - -* An object allocated on the heap must never store a reference/pointer - to another object. - -* A class designed to be allocated only on the stack can store a - reference/pointer to another object. Here is the list of - such classes: - - - IMessage and its derived classes: All the messages are allocated - on the stack. - - -Pointers --------- - -* As we are targeting C++03 (for VS2008 and LSB compatibility), use - "std::unique_ptr<>" and "boost::shared_ptr<>" (*not* - "std::shared_ptr<>"). We provide an implementation of std::unique_ptr for - pre-C++11 compilers. - -* The fact of transfering the ownership of one object to another must - be tagged by naming the method "Acquire...()", and by providing a - raw pointer. - -* Use "std::unique_ptr<>" if the goal is to internally store a pointer - whose lifetime corresponds to the host object. - -* The use of "boost::weak_ptr<>" should be restricted to - oracle/message handling. - -* The use of "boost::shared_ptr<>" should be minimized to avoid - clutter. The "loaders" and "data parts" objects must however - be created as "boost::shared_ptr<>". - - -Global context --------------- - -* As the global Stone context can be created/destroyed by other - languages than C++, we don't use a "boost:shared_ptr<>".
--- a/OrthancStone/Docs/stone-object-model-reference.md Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,429 +0,0 @@ -## Scene2D and viewport-related object reference - -### `Scene2D` - -Represents a collection of layers that display 2D data. - -These layers must implement `ISceneLayer` - -The layers must be created externally and set to a specific Z-order index -with the `SetLayer` method. - -The `Scene2D` object merely acts as a layer container. It has no rendering -or layer creation facility on its own. - -The `Scene2D` contains an `AffineTransform2D` structure that defines how -the various layer item coordinates are transformed before being displayed -on the viewport (aka canvas) - -It is up to each layer type-specific renderer to choose how this transformation -is used. See the various kinds of layer below for more details. - -Examining the `Scene2D` contents can be done either by implementing the -`Scene2D::IVisitor` interface and calling `Apply(IVisitor& visitor)` or by -iterating between `GetMinDepth()` and `GetMaxDepth()` and calling the -`ISceneLayer& GetLayer(int depth)` getter. - -### `ISceneLayer` - -Interface that must be implemented by `Scene2D` layers. This is a closed list -that, as of 2020-03, contains: - -``` - Type_InfoPanel, - Type_ColorTexture, - Type_Polyline, - Type_Text, - Type_FloatTexture, - Type_LookupTableTexture -``` - -Please note that this interface mandates the implementation of a `GetRevision` -method returning an `uint64_t`. - -The idea is that when a model gets converted to a set of `ISceneLayer` -instances, changes in the model that result in changes to the layers must -increase the revision number of these layers. - -That allows the rendering process to safely assume that a given layers whose -revision does not change hasn't been modified (this helps with caching). - -Every mutable method in `ISceneLayer` instances that possibly change the visual -representation of an `ISceneLayer` must increase this revision number. - -### Implementation: `FloatTextureSceneLayer` - -Layer that renders an `Orthanc::ImageAccessor` object that must be convertible -to `Float32` image. - -The constructor only uses the image accessor to perform a copy. It can safely -be deleted afterwards. - -The input values are mapped to the output values by taking into account various -properties that can be modified with: - -- `SetWindowing`: uses windowing presets like "bone" or "lung" -- `SetCustomWindowing`: with manual window center and width -- `SetInverted`: toggles black <-> white inversion after windowing -- `SetApplyLog`: uses a non-linear response curve described in - https://theailearner.com/2019/01/01/log-transformation/ that expands contrast - in dark areas while compressing contrast in bright ones. This is **not** - implemented in the OpenGL renderer! - -The corresponding renderers are `OpenGLFloatTextureRenderer` and -`CairoFloatTextureRenderer`. The scene transformation is applied during -rendering. - -### Implementation: `ColorTextureSceneLayer` - -Layer that renders an `Orthanc::ImageAccessor` object an RGBA image (alpha must -be premultiplied). - -The constructor only uses the image accessor to perform a copy. It can safely -be deleted afterwards. - -The corresponding renderers are `OpenGLColorTextureRenderer` and -`CairoColorTextureRenderer`. The scene transformation is applied during -rendering. - -### Implementation: `LookupTableTextureSceneLayer` - -Layer that renders an `Orthanc::ImageAccessor` object that must be convertible -to `Float32` image. - -The constructor only uses the image accessor to perform a copy. It can safely -be deleted afterwards. - -The final on-screen color of each pixel is determined by passing the input -`Float32` value through a 256-entry look-up table (LUT) that can be passed as -an array of either 256 x 3 bytes (for opaque RGB colors) or 256 x 4 bytes (for -RGBA pixels). The LUT is not specified at construction time, but with -calls to `SetLookupTable` or `SetLookupTableGrayscale` (that fills the LUT -with a gradient from black to white, fully opaque) - -The range of input values that is mapped to the entirety of the LUT is, by -default, the full image range, but can be customized with `SetRange`. - -The corresponding renderers are `OpenGLLookupTableTextureRenderer` and -`CairoLookupTableTextureRenderer`. The scene transformation is applied during -rendering. - -### Implementation: `PolylineSceneLayer` - -Layer that renders vector-based polygonal lines. - -Polylines can be added with the `AddChain` method, that accepts a `Chain`, that -is a typedef to `std::vector<ScenePoint2D>`, a flag to specify whether the -chain must be automatically close (last point of the vector connected to the -first one) and the chain color (a `Color` structure). - -Please note that the line thickness is, contrary to the color, specified -per-chain but rather per-layer. - -If you need multiple line thicknesses, multiple `PolylineSceneLayer` must be -created. - -The corresponding renderers are `OpenGLAdvancedPolylineRenderer` and -`CairoPolylineRenderer`. The scene transformation is applied during -rendering. - -### Implementation: `TextSceneLayer` - -This layers renders a paragraph of text. - -The inputs to the layer can be changed after creation and are: -- The text iself, supplied as an UTF-8 encoded string in `SetText` -- The font used for rendering, set by `SetFontIndex`. -- The text anchoring, through `SetAnchor`: the text can be anchored to - various positions, such as top lef, center, bottom center,... These - various anchors are part of the `BitmapAnchor` enumeration. -- The text position, relative to its anchor, through `SetPosition`. - -The font is supplied as an index. This is an index in the set of fonts -that has been registered in the viewport compositor. The following code -shows how to set such a font: - -``` -std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_.Lock()); -lock->GetCompositor().SetFont(0, - Orthanc::EmbeddedResources::UBUNTU_FONT, - 32, Orthanc::Encoding_Latin1); -// where 32 is the font size in pixels -``` - -This call uses the embedded `UBUNTU_FONT` resource that has been defined in -the `CMakeLists.txt` file with: - -``` -EmbedResources( - UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf -) -``` - -Please note that you must supply a font: there is no default font provided by -the OpenGL or Cairo compositors. - -The corresponding renderers are `OpenGLTextRenderer` and -`CairoTextRenderer`. The scene transformation is not applied during rendering, -because the text anchoring, position and scaling are computed relative to the -viewport/canvas. - -### Implementation: `InfoPanelSceneLayer` - -This layer is designed to display an image, supplied through an -`Orthanc::ImageAccessor` reference (only used at construction time). - -The image is not transformed according to the normal layer transformation but -is rather positioned relative to the canvas, with the same mechanism as the -`TextSceneLayer` described above. - -The image position is specified with the sole means of the `SetAnchor` method. - -The corresponding renderers are `OpenGLInfoPanelRenderer` and -`CairoInfoPanelRenderer`. - -### `IViewport` - -https://bitbucket.org/sjodogne/orthanc-stone/src/broker/Framework/Viewport/IViewport.h - -(**not** the one in `Deprecated`) -- Implemented by classes that: - - manage the on-screen display of a `Scene2D` trough a compositor. - - Own the `ICompositor` object that performs the rendering. - - Own the `Scene2D` (TODO: currently through `ViewportController` --> `Scene2D`) - - Provide a `Lock` method that returns a RAII, that must be kept alive when - modifying the underlying objects (controller, compositor, scene), but not - longer. - -#### Implementation: `SdlOpenGLViewport` -- Implementation of a viewport rendered on a SDL window, that uses OpenGL for - rendering. -- Instantiating this object creates an SDL window. Automatic scaling for hiDPI - displays can be toggled on or off. - -#### Implementation: `WebGLViewport` -- Implementation of a viewport rendered on a DOM canvas, that uses OpenGL for - rendering. -- Contrary to the SDL OpenGL viewport, the canvas must already be existing - when the ctor is called. - -### `ICompositor` -The interface providing a rendering service for `Scene2D` objects. - -**Subclasses:** `CairoCompositor`, `OpenGLCompositor` - -You do not need to create compositor instances. They are created for you when -instantiating a viewport. - -### `ViewportController` -This concrete class is instantiated by its `IViewport` owner. - -**TODO:** its functionality is not well defined and should be moved into the -viewport base class. Support for measuring tools should be moved to a special -interactor. - -- contains: - - array of `MeasureTool` - - ref to `IViewport` - - `activeTracker_` - - owns a `Scene2D` - - weak ref to `UndoStack` - - cached `canvasToSceneFactor_` - -- contains logic to: - - pass commands to undostack (trivial) - - logic to locate `MeasureTool` in the HitTest - - OTOH, the meat of the measuring tool logic (highlighting etc..) is - done in app-specific code (`VolumeSlicerWidget`) - - accept new Scene transform and notify listeners - - **the code that uses the interactor** (`HandleMousePress`) is only - called by the new `WebAssemblyViewport` !!! **TODO** clean this mess - -### `IViewportInteractor` -- must provide logic to respond to `CreateTracker` - -### `DefaultViewportInteractor` -- provides Pan+Rotate+Zoom trackers - -### `WebGLViewportsRegistry` - -This class is a singleton (accessible through `GetWebGLViewportsRegistry()` -that deals with context losses in the WebGL contexts. - -You use it by creating a WebGLViewport in the following fashion: - -``` -boost::shared_ptr<OrthancStone::WebGLViewport> viewport( - OrthancStone::GetWebGLViewportsRegistry().Add(canvasId)); -``` - -## Source data related - -### `IVolumeSlicer` - -A very simple interface with a single method: -`IVolumeSlicer::IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)` - -### `IVolumeSlicer::IExtractedSlice` - -On a slice has been extracted from a volume by an `IVolumeSlicer`, it can -report its *revision number*. - -If another call to `ExtractSlice` with the same cutting plane is made, but -the returned slice revision is different, it means that the volume has -changed and the scene layer must be refreshed. - -Please see `VolumeSceneLayerSource::Update` to check how this logic is -implemented. - - -### `OrthancSeriesVolumeProgressiveLoader` - -This class implements `IVolumeSlicer` (and `IObservable`) and can be used to -load a volume stored in a Dicom series on an Orthanc server. - -Over the course of the series loading, various notifications are sent: - -The first one is `OrthancStone::DicomVolumeImage::GeometryReadyMessage` that -is sent when the volume extent and geometric properties are known. - -Then, as slices get loaded and the volume is filled, -`OrthancStone::DicomVolumeImage::ContentUpdatedMessage` are sent. - -Once all the highest-quality slices have been loaded, the -`OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality` -notification is sent. - -Please note that calling `ExtractSlice` *before* the geometry is loaded will -yield an instance of `InvalidSlice` that cannot be used to create a layer. - -On the other hand, - -### `VolumeSceneLayerSource` - -This class makes the bridge between a volume (supplied by an `IVolumeSlicer` -interface) and a `Scene2D`. - -Please note that the bulk of the work is done the objects implementing -`IVolumeSlicer` and this object merely connects things together. - -For instance, deciding whether an image (texture) or vector (polyline) layer -is done by the `IVolumeSlicer` implementation. - -- contains: - - reference to Scene2D - - `layerIndex_` (fixed at ctor) that is the index, in the Scene2D layer - stack, of the layer that will be created/updated - - `IVolumeSlicer` - -- contains logic to: - - extract a slice from the slicer and set/refresh the Scene2D layer at - the supplied `layerIndex_` - - refresh this based on the slice revision or configuration revision - - accept a configuration that will be applied to the layer - - the `Update()` method will - -## Updates and the configurators - -`ISceneLayer` does not expose mutable methods. - -The way to change a layer once it has been created is through configurator -objets. - -If you plan to set (even only once) or modify some layer properties after -layer creation, you need to create a matching configurator objet. - -For instance, in the `VolumeSceneLayerSource`, the `SetConfigurator` method -will store a `ILayerStyleConfigurator* configurator_`. - -In the `OrthancView` ctor, you can see how it is used: - -``` -std::unique_ptr<GrayscaleStyleConfigurator> style( - new GrayscaleStyleConfigurator); - -style->SetLinearInterpolation(true); - -...<some more code>... - -std::unique_ptr<LookupTableStyleConfigurator> config( - new LookupTableStyleConfigurator); - -config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); - -``` - -The configurator type are created according to the type of layer.¸ - -Later, in `VolumeSceneLayerSource::Update(const CoordinateSystem3D& plane)`, -if the cutting plane has **not** changed and if the layer revision has **not** -changed, we test `configurator_->GetRevision() != lastConfiguratorRevision_` -and, if different, we call `configurator_->ApplyStyle(scene_.GetLayer(layerDepth_));` - -This allows to change layer properties that do not depend on the layer model -contents. - -On the other hand, if the layer revision has changed, when compared to the -last time it has been rendered (stored in `lastRevision_`), then we need to -ask the slice to create a brand new layer. - -Another way to see it is that layer rendering depend on model data and view -data. The model data is not mutable in the layer and, if the model changes, the -layer must be recreated. - -If only the view properties change (the configurator), we call ApplyStyle -(that **will** mutate some of the layer internals) - -Please note that the renderer does **not** know about the configurator : the -renderer uses properies in the layer and does not care whether those have -been set once at construction time or at every frame (configuration time). - - -## Cookbook - -### Simple application - -#### Building - -In order to create a Stone application, you need to: - -- CMake-based application: - ``` - include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneConfiguration.cmake) - ``` - with this library target that you have to define: - ``` - add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES}) - ``` - then link with this library: - ``` - target_link_libraries(MyStoneApplication OrthancStone) - ``` - -Building is supported with emscripten, Visual C++ (>= 9.0), gcc... - -emscripten recommended version >= 1.38.41 - -These are very rough guidelines. See the `Samples` folder for actual examples. - -#### Structure - -The code requires a loader (object that ) - -Initialize: - -``` -Orthanc::Logging::Initialize(); -Orthanc::Logging::EnableInfoLevel(true); -``` -Call, in WASM: -``` -DISPATCH_JAVASCRIPT_EVENT("StoneInitialized"); -``` - -# Notes - -- It is NOT possible to abandon the existing loaders : they contain too much loader-specific getters - - -
--- a/OrthancStone/Resources/CMake/LinuxStandardBaseUic.py Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -#!/usr/bin/env python - -import subprocess -import sys - -if len(sys.argv) <= 1: - sys.stderr.write('Please provide arguments for uic\n') - sys.exit(-1) - -path = '' -pos = 1 -while pos < len(sys.argv): - if sys.argv[pos].startswith('-'): - pos += 2 - else: - path = sys.argv[pos] - break - -if len(path) == 0: - sys.stderr.write('Unable to find the input file in the arguments to uic\n') - sys.exit(-1) - -with open(path, 'r') as f: - lines = f.read().split('\n') - if (len(lines) > 1 and - lines[0].startswith('<?')): - content = '\n'.join(lines[1:]) - else: - content = '\n'.join(lines) - -# Remove the source file from the arguments -args = sys.argv[1:pos] + sys.argv[pos+1:] - -p = subprocess.Popen([ '/opt/lsb/bin/uic' ] + args, - stdin = subprocess.PIPE) -p.communicate(input = content)
--- a/OrthancStone/Resources/Colormaps/GenerateColormaps.py Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -#!/usr/bin/python - -import array -import matplotlib.pyplot as plt - -def GenerateColormap(name): - colormap = [] - - for gray in range(256): - if name == 'red': - color = (gray / 255.0, 0, 0) - elif name == 'green': - color = (0, gray / 255.0, 0) - elif name == 'blue': - color = (0, 0, gray / 255.0) - else: - color = plt.get_cmap(name) (gray) - - colormap += map(lambda k: int(round(color[k] * 255)), range(3)) - - colormap[0] = 0 - colormap[1] = 0 - colormap[2] = 0 - - return array.array('B', colormap).tostring() - - -for name in [ - 'hot', - 'jet', - 'blue', - 'green', - 'red', -]: - with open('%s.lut' % name, 'w') as f: - f.write(GenerateColormap(name))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Documentation/Conventions.txt Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,88 @@ + +Some notes about the lifetime of objects +======================================== + +Stone applications +------------------ + +A typical Stone application can be split in 3 parts: + +1- The "loaders part" and the associated "IOracle", that communicate + through "IMessage" objects. The lifetime of these objects is + governed by the "IStoneContext". + +2- The "data part" holds the data loaded by the "loaders part". The + related objects must not be aware of the oracle, neither of the + messages. It is up to the user application to store these objects. + +3- The "viewport part" is based upon the "Scene2D" class. + + +Multithreading +-------------- + +* Stone makes the hypothesis that its objects live in a single thread. + All the content of the "Framework" folder (with the exception of + the "Oracle" stuff) must not use "boost::thread". + +* The "IOracleCommand" classes represent commands that must be + executed asynchronously from the Stone thread. Their actual + execution is done by the "IOracle". + +* In WebAssembly, the "IOracle" corresponds to the "html5.h" + facilities (notably for the Fetch API). There is no mutex here, as + JavaScript is inherently single-threaded. + +* In plain C++ applications, the "IOracle" corresponds to a FIFO queue + of commands that are executed by a pool of threads. The Stone + context holds a global mutex, that must be properly locked by the + user application, and by the "IOracle" when it sends back messages + to the Stone loaders (cf. class "IMessageEmitter"). + +* Multithreading is thus achieved by defining new oracle commands by + subclassing "IOracleCommand", then by defining a way to execute them + (cf. class "GenericCommandRunner"). + + +References between objects +-------------------------- + +* An object allocated on the heap must never store a reference/pointer + to another object. + +* A class designed to be allocated only on the stack can store a + reference/pointer to another object. Here is the list of + such classes: + + - IMessage and its derived classes: All the messages are allocated + on the stack. + + +Pointers +-------- + +* As we are targeting C++03 (for VS2008 and LSB compatibility), use + "std::unique_ptr<>" and "boost::shared_ptr<>" (*not* + "std::shared_ptr<>"). We provide an implementation of std::unique_ptr for + pre-C++11 compilers. + +* The fact of transfering the ownership of one object to another must + be tagged by naming the method "Acquire...()", and by providing a + raw pointer. + +* Use "std::unique_ptr<>" if the goal is to internally store a pointer + whose lifetime corresponds to the host object. + +* The use of "boost::weak_ptr<>" should be restricted to + oracle/message handling. + +* The use of "boost::shared_ptr<>" should be minimized to avoid + clutter. The "loaders" and "data parts" objects must however + be created as "boost::shared_ptr<>". + + +Global context +-------------- + +* As the global Stone context can be created/destroyed by other + languages than C++, we don't use a "boost:shared_ptr<>".
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Resources/Documentation/stone-object-model-reference.md Thu Oct 22 16:17:10 2020 +0200 @@ -0,0 +1,429 @@ +## Scene2D and viewport-related object reference + +### `Scene2D` + +Represents a collection of layers that display 2D data. + +These layers must implement `ISceneLayer` + +The layers must be created externally and set to a specific Z-order index +with the `SetLayer` method. + +The `Scene2D` object merely acts as a layer container. It has no rendering +or layer creation facility on its own. + +The `Scene2D` contains an `AffineTransform2D` structure that defines how +the various layer item coordinates are transformed before being displayed +on the viewport (aka canvas) + +It is up to each layer type-specific renderer to choose how this transformation +is used. See the various kinds of layer below for more details. + +Examining the `Scene2D` contents can be done either by implementing the +`Scene2D::IVisitor` interface and calling `Apply(IVisitor& visitor)` or by +iterating between `GetMinDepth()` and `GetMaxDepth()` and calling the +`ISceneLayer& GetLayer(int depth)` getter. + +### `ISceneLayer` + +Interface that must be implemented by `Scene2D` layers. This is a closed list +that, as of 2020-03, contains: + +``` + Type_InfoPanel, + Type_ColorTexture, + Type_Polyline, + Type_Text, + Type_FloatTexture, + Type_LookupTableTexture +``` + +Please note that this interface mandates the implementation of a `GetRevision` +method returning an `uint64_t`. + +The idea is that when a model gets converted to a set of `ISceneLayer` +instances, changes in the model that result in changes to the layers must +increase the revision number of these layers. + +That allows the rendering process to safely assume that a given layers whose +revision does not change hasn't been modified (this helps with caching). + +Every mutable method in `ISceneLayer` instances that possibly change the visual +representation of an `ISceneLayer` must increase this revision number. + +### Implementation: `FloatTextureSceneLayer` + +Layer that renders an `Orthanc::ImageAccessor` object that must be convertible +to `Float32` image. + +The constructor only uses the image accessor to perform a copy. It can safely +be deleted afterwards. + +The input values are mapped to the output values by taking into account various +properties that can be modified with: + +- `SetWindowing`: uses windowing presets like "bone" or "lung" +- `SetCustomWindowing`: with manual window center and width +- `SetInverted`: toggles black <-> white inversion after windowing +- `SetApplyLog`: uses a non-linear response curve described in + https://theailearner.com/2019/01/01/log-transformation/ that expands contrast + in dark areas while compressing contrast in bright ones. This is **not** + implemented in the OpenGL renderer! + +The corresponding renderers are `OpenGLFloatTextureRenderer` and +`CairoFloatTextureRenderer`. The scene transformation is applied during +rendering. + +### Implementation: `ColorTextureSceneLayer` + +Layer that renders an `Orthanc::ImageAccessor` object an RGBA image (alpha must +be premultiplied). + +The constructor only uses the image accessor to perform a copy. It can safely +be deleted afterwards. + +The corresponding renderers are `OpenGLColorTextureRenderer` and +`CairoColorTextureRenderer`. The scene transformation is applied during +rendering. + +### Implementation: `LookupTableTextureSceneLayer` + +Layer that renders an `Orthanc::ImageAccessor` object that must be convertible +to `Float32` image. + +The constructor only uses the image accessor to perform a copy. It can safely +be deleted afterwards. + +The final on-screen color of each pixel is determined by passing the input +`Float32` value through a 256-entry look-up table (LUT) that can be passed as +an array of either 256 x 3 bytes (for opaque RGB colors) or 256 x 4 bytes (for +RGBA pixels). The LUT is not specified at construction time, but with +calls to `SetLookupTable` or `SetLookupTableGrayscale` (that fills the LUT +with a gradient from black to white, fully opaque) + +The range of input values that is mapped to the entirety of the LUT is, by +default, the full image range, but can be customized with `SetRange`. + +The corresponding renderers are `OpenGLLookupTableTextureRenderer` and +`CairoLookupTableTextureRenderer`. The scene transformation is applied during +rendering. + +### Implementation: `PolylineSceneLayer` + +Layer that renders vector-based polygonal lines. + +Polylines can be added with the `AddChain` method, that accepts a `Chain`, that +is a typedef to `std::vector<ScenePoint2D>`, a flag to specify whether the +chain must be automatically close (last point of the vector connected to the +first one) and the chain color (a `Color` structure). + +Please note that the line thickness is, contrary to the color, specified +per-chain but rather per-layer. + +If you need multiple line thicknesses, multiple `PolylineSceneLayer` must be +created. + +The corresponding renderers are `OpenGLAdvancedPolylineRenderer` and +`CairoPolylineRenderer`. The scene transformation is applied during +rendering. + +### Implementation: `TextSceneLayer` + +This layers renders a paragraph of text. + +The inputs to the layer can be changed after creation and are: +- The text iself, supplied as an UTF-8 encoded string in `SetText` +- The font used for rendering, set by `SetFontIndex`. +- The text anchoring, through `SetAnchor`: the text can be anchored to + various positions, such as top lef, center, bottom center,... These + various anchors are part of the `BitmapAnchor` enumeration. +- The text position, relative to its anchor, through `SetPosition`. + +The font is supplied as an index. This is an index in the set of fonts +that has been registered in the viewport compositor. The following code +shows how to set such a font: + +``` +std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_.Lock()); +lock->GetCompositor().SetFont(0, + Orthanc::EmbeddedResources::UBUNTU_FONT, + 32, Orthanc::Encoding_Latin1); +// where 32 is the font size in pixels +``` + +This call uses the embedded `UBUNTU_FONT` resource that has been defined in +the `CMakeLists.txt` file with: + +``` +EmbedResources( + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf +) +``` + +Please note that you must supply a font: there is no default font provided by +the OpenGL or Cairo compositors. + +The corresponding renderers are `OpenGLTextRenderer` and +`CairoTextRenderer`. The scene transformation is not applied during rendering, +because the text anchoring, position and scaling are computed relative to the +viewport/canvas. + +### Implementation: `InfoPanelSceneLayer` + +This layer is designed to display an image, supplied through an +`Orthanc::ImageAccessor` reference (only used at construction time). + +The image is not transformed according to the normal layer transformation but +is rather positioned relative to the canvas, with the same mechanism as the +`TextSceneLayer` described above. + +The image position is specified with the sole means of the `SetAnchor` method. + +The corresponding renderers are `OpenGLInfoPanelRenderer` and +`CairoInfoPanelRenderer`. + +### `IViewport` + +https://bitbucket.org/sjodogne/orthanc-stone/src/broker/Framework/Viewport/IViewport.h + +(**not** the one in `Deprecated`) +- Implemented by classes that: + - manage the on-screen display of a `Scene2D` trough a compositor. + - Own the `ICompositor` object that performs the rendering. + - Own the `Scene2D` (TODO: currently through `ViewportController` --> `Scene2D`) + - Provide a `Lock` method that returns a RAII, that must be kept alive when + modifying the underlying objects (controller, compositor, scene), but not + longer. + +#### Implementation: `SdlOpenGLViewport` +- Implementation of a viewport rendered on a SDL window, that uses OpenGL for + rendering. +- Instantiating this object creates an SDL window. Automatic scaling for hiDPI + displays can be toggled on or off. + +#### Implementation: `WebGLViewport` +- Implementation of a viewport rendered on a DOM canvas, that uses OpenGL for + rendering. +- Contrary to the SDL OpenGL viewport, the canvas must already be existing + when the ctor is called. + +### `ICompositor` +The interface providing a rendering service for `Scene2D` objects. + +**Subclasses:** `CairoCompositor`, `OpenGLCompositor` + +You do not need to create compositor instances. They are created for you when +instantiating a viewport. + +### `ViewportController` +This concrete class is instantiated by its `IViewport` owner. + +**TODO:** its functionality is not well defined and should be moved into the +viewport base class. Support for measuring tools should be moved to a special +interactor. + +- contains: + - array of `MeasureTool` + - ref to `IViewport` + - `activeTracker_` + - owns a `Scene2D` + - weak ref to `UndoStack` + - cached `canvasToSceneFactor_` + +- contains logic to: + - pass commands to undostack (trivial) + - logic to locate `MeasureTool` in the HitTest + - OTOH, the meat of the measuring tool logic (highlighting etc..) is + done in app-specific code (`VolumeSlicerWidget`) + - accept new Scene transform and notify listeners + - **the code that uses the interactor** (`HandleMousePress`) is only + called by the new `WebAssemblyViewport` !!! **TODO** clean this mess + +### `IViewportInteractor` +- must provide logic to respond to `CreateTracker` + +### `DefaultViewportInteractor` +- provides Pan+Rotate+Zoom trackers + +### `WebGLViewportsRegistry` + +This class is a singleton (accessible through `GetWebGLViewportsRegistry()` +that deals with context losses in the WebGL contexts. + +You use it by creating a WebGLViewport in the following fashion: + +``` +boost::shared_ptr<OrthancStone::WebGLViewport> viewport( + OrthancStone::GetWebGLViewportsRegistry().Add(canvasId)); +``` + +## Source data related + +### `IVolumeSlicer` + +A very simple interface with a single method: +`IVolumeSlicer::IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)` + +### `IVolumeSlicer::IExtractedSlice` + +On a slice has been extracted from a volume by an `IVolumeSlicer`, it can +report its *revision number*. + +If another call to `ExtractSlice` with the same cutting plane is made, but +the returned slice revision is different, it means that the volume has +changed and the scene layer must be refreshed. + +Please see `VolumeSceneLayerSource::Update` to check how this logic is +implemented. + + +### `OrthancSeriesVolumeProgressiveLoader` + +This class implements `IVolumeSlicer` (and `IObservable`) and can be used to +load a volume stored in a Dicom series on an Orthanc server. + +Over the course of the series loading, various notifications are sent: + +The first one is `OrthancStone::DicomVolumeImage::GeometryReadyMessage` that +is sent when the volume extent and geometric properties are known. + +Then, as slices get loaded and the volume is filled, +`OrthancStone::DicomVolumeImage::ContentUpdatedMessage` are sent. + +Once all the highest-quality slices have been loaded, the +`OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality` +notification is sent. + +Please note that calling `ExtractSlice` *before* the geometry is loaded will +yield an instance of `InvalidSlice` that cannot be used to create a layer. + +On the other hand, + +### `VolumeSceneLayerSource` + +This class makes the bridge between a volume (supplied by an `IVolumeSlicer` +interface) and a `Scene2D`. + +Please note that the bulk of the work is done the objects implementing +`IVolumeSlicer` and this object merely connects things together. + +For instance, deciding whether an image (texture) or vector (polyline) layer +is done by the `IVolumeSlicer` implementation. + +- contains: + - reference to Scene2D + - `layerIndex_` (fixed at ctor) that is the index, in the Scene2D layer + stack, of the layer that will be created/updated + - `IVolumeSlicer` + +- contains logic to: + - extract a slice from the slicer and set/refresh the Scene2D layer at + the supplied `layerIndex_` + - refresh this based on the slice revision or configuration revision + - accept a configuration that will be applied to the layer + - the `Update()` method will + +## Updates and the configurators + +`ISceneLayer` does not expose mutable methods. + +The way to change a layer once it has been created is through configurator +objets. + +If you plan to set (even only once) or modify some layer properties after +layer creation, you need to create a matching configurator objet. + +For instance, in the `VolumeSceneLayerSource`, the `SetConfigurator` method +will store a `ILayerStyleConfigurator* configurator_`. + +In the `OrthancView` ctor, you can see how it is used: + +``` +std::unique_ptr<GrayscaleStyleConfigurator> style( + new GrayscaleStyleConfigurator); + +style->SetLinearInterpolation(true); + +...<some more code>... + +std::unique_ptr<LookupTableStyleConfigurator> config( + new LookupTableStyleConfigurator); + +config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); + +``` + +The configurator type are created according to the type of layer.¸ + +Later, in `VolumeSceneLayerSource::Update(const CoordinateSystem3D& plane)`, +if the cutting plane has **not** changed and if the layer revision has **not** +changed, we test `configurator_->GetRevision() != lastConfiguratorRevision_` +and, if different, we call `configurator_->ApplyStyle(scene_.GetLayer(layerDepth_));` + +This allows to change layer properties that do not depend on the layer model +contents. + +On the other hand, if the layer revision has changed, when compared to the +last time it has been rendered (stored in `lastRevision_`), then we need to +ask the slice to create a brand new layer. + +Another way to see it is that layer rendering depend on model data and view +data. The model data is not mutable in the layer and, if the model changes, the +layer must be recreated. + +If only the view properties change (the configurator), we call ApplyStyle +(that **will** mutate some of the layer internals) + +Please note that the renderer does **not** know about the configurator : the +renderer uses properies in the layer and does not care whether those have +been set once at construction time or at every frame (configuration time). + + +## Cookbook + +### Simple application + +#### Building + +In order to create a Stone application, you need to: + +- CMake-based application: + ``` + include(${STONE_SOURCES_DIR}/Resources/CMake/OrthancStoneConfiguration.cmake) + ``` + with this library target that you have to define: + ``` + add_library(OrthancStone STATIC ${ORTHANC_STONE_SOURCES}) + ``` + then link with this library: + ``` + target_link_libraries(MyStoneApplication OrthancStone) + ``` + +Building is supported with emscripten, Visual C++ (>= 9.0), gcc... + +emscripten recommended version >= 1.38.41 + +These are very rough guidelines. See the `Samples` folder for actual examples. + +#### Structure + +The code requires a loader (object that ) + +Initialize: + +``` +Orthanc::Logging::Initialize(); +Orthanc::Logging::EnableInfoLevel(true); +``` +Call, in WASM: +``` +DISPATCH_JAVASCRIPT_EVENT("StoneInitialized"); +``` + +# Notes + +- It is NOT possible to abandon the existing loaders : they contain too much loader-specific getters + + +
--- a/OrthancStone/Resources/Graveyard/Messaging/CurlOrthancConnection.cpp Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "CurlOrthancConnection.h" - -#if ORTHANC_ENABLE_CURL == 1 - -#include "../../Resources/Orthanc/Core/HttpClient.h" -#include "../../Resources/Orthanc/Core/OrthancException.h" - -namespace OrthancStone -{ - void CurlOrthancConnection::RestApiGet(std::string& result, - const std::string& uri) - { - /** - * TODO: This function sometimes crashes if compiled with - * MinGW-W64 (32 bit) in Release mode, on Windows XP. Introducing - * a mutex here fixes the issue. Not sure of what is the - * culprit. Maybe a bug in a old version of MinGW? - **/ - - Orthanc::HttpClient client(parameters_, uri); - - // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc) - client.SetRedirectionFollowed(false); - - if (!client.Apply(result)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); - } - } - - - void CurlOrthancConnection::RestApiPost(std::string& result, - const std::string& uri, - const std::string& body) - { - Orthanc::HttpClient client(parameters_, uri); - - // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc) - client.SetRedirectionFollowed(false); - - client.SetBody(body); - client.SetMethod(Orthanc::HttpMethod_Post); - - if (!client.Apply(result)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); - } - } -} - -#endif
--- a/OrthancStone/Resources/Graveyard/Messaging/CurlOrthancConnection.h Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IOrthancConnection.h" - -#if ORTHANC_ENABLE_CURL == 1 - -#include "../../Resources/Orthanc/Core/WebServiceParameters.h" - -namespace OrthancStone -{ - class CurlOrthancConnection : public IOrthancConnection - { - private: - Orthanc::WebServiceParameters parameters_; - - public: - CurlOrthancConnection(const Orthanc::WebServiceParameters& parameters) : - parameters_(parameters) - { - } - - const Orthanc::WebServiceParameters& GetParameters() const - { - return parameters_; - } - - virtual void RestApiGet(std::string& result, - const std::string& uri); - - virtual void RestApiPost(std::string& result, - const std::string& uri, - const std::string& body); - }; -} - -#endif
--- a/OrthancStone/Resources/Graveyard/Messaging/IOrthancConnection.h Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../Toolbox/IThreadSafety.h" - -#include <string> - -namespace OrthancStone -{ - // Derived classes must be thread-safe - class IOrthancConnection : public IThreadSafe - { - public: - virtual void RestApiGet(std::string& result, - const std::string& uri) = 0; - - virtual void RestApiPost(std::string& result, - const std::string& uri, - const std::string& body) = 0; - }; -}
--- a/OrthancStone/Resources/Graveyard/ReferenceLineFactory.cpp Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "ReferenceLineFactory.h" - -#include "LineLayerRenderer.h" - -namespace OrthancStone -{ - ReferenceLineFactory::ReferenceLineFactory(SliceViewerWidget& owner, - SliceViewerWidget& sibling) : - owner_(owner), - sibling_(sibling), - hasLayerIndex_(false) - { - style_.SetColor(0, 255, 0); - slice_ = sibling.GetSlice(); - sibling_.Register(*this); - } - - - void ReferenceLineFactory::NotifySliceContentChange(const SliceViewerWidget& source, - const SliceGeometry& slice) - { - if (&source == &sibling_) - { - SetSlice(slice); - } - } - - - void ReferenceLineFactory::SetLayerIndex(size_t layerIndex) - { - hasLayerIndex_ = true; - layerIndex_ = layerIndex; - } - - - void ReferenceLineFactory::SetStyle(const RenderStyle& style) - { - style_ = style; - } - - - RenderStyle ReferenceLineFactory::GetRenderStyle() - { - return style_; - } - - - void ReferenceLineFactory::SetSlice(const SliceGeometry& slice) - { - slice_ = slice; - - if (hasLayerIndex_) - { - owner_.InvalidateLayer(layerIndex_); - } - } - - - ILayerRenderer* ReferenceLineFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice) - { - Vector p, d; - - // Compute the line of intersection between the two slices - if (!GeometryToolbox::IntersectTwoPlanes(p, d, - slice_.GetOrigin(), slice_.GetNormal(), - viewportSlice.GetOrigin(), viewportSlice.GetNormal())) - { - // The two slice are parallel, don't try and display the intersection - return NULL; - } - - double x1, y1, x2, y2; - viewportSlice.ProjectPoint(x1, y1, p); - viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d); - - double sx1, sy1, sx2, sy2; - owner_.GetView().GetSceneExtent(sx1, sy1, sx2, sy2); - - if (GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, - x1, y1, x2, y2, - sx1, sy1, sx2, sy2)) - { - std::unique_ptr<ILayerRenderer> layer(new LineLayerRenderer(x1, y1, x2, y2)); - layer->SetLayerStyle(style_); - return layer.release(); - } - else - { - // Parallel slices - return NULL; - } - } - - - ISliceableVolume& ReferenceLineFactory::GetSourceVolume() const - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - - void ReferenceLineFactory::Configure(SliceViewerWidget& a, - SliceViewerWidget& b) - { - { - size_t layerIndex; - ILayerRendererFactory& factory = a.AddLayer(layerIndex, new ReferenceLineFactory(a, b)); - dynamic_cast<ReferenceLineFactory&>(factory).SetLayerIndex(layerIndex); - } - - { - size_t layerIndex; - ILayerRendererFactory& factory = b.AddLayer(layerIndex, new ReferenceLineFactory(b, a)); - dynamic_cast<ReferenceLineFactory&>(factory).SetLayerIndex(layerIndex); - } - } -}
--- a/OrthancStone/Resources/Graveyard/ReferenceLineFactory.h Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../Widgets/SliceViewerWidget.h" - -namespace OrthancStone -{ - class ReferenceLineFactory : - public ILayerRendererFactory, - public SliceViewerWidget::ISliceObserver - { - private: - SliceViewerWidget& owner_; - SliceViewerWidget& sibling_; - SliceGeometry slice_; - RenderStyle style_; - bool hasLayerIndex_; - size_t layerIndex_; - - - public: - ReferenceLineFactory(SliceViewerWidget& owner, - SliceViewerWidget& sibling); - - virtual void NotifySliceContentChange(const SliceViewerWidget& source, - const SliceGeometry& slice); - - void SetLayerIndex(size_t layerIndex); - - void SetStyle(const RenderStyle& style); - - RenderStyle GetRenderStyle(); - - void SetSlice(const SliceGeometry& slice); - - virtual bool GetExtent(double& x1, - double& y1, - double& x2, - double& y2, - const SliceGeometry& viewportSlice) - { - return false; - } - - virtual ILayerRenderer* CreateLayerRenderer(const SliceGeometry& viewportSlice); - - virtual bool HasSourceVolume() const - { - return false; - } - - virtual ISliceableVolume& GetSourceVolume() const; - - static void Configure(SliceViewerWidget& a, - SliceViewerWidget& b); - }; -}
--- a/OrthancStone/Resources/Graveyard/Threading/BinarySemaphore.cpp Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "BinarySemaphore.h" - -namespace OrthancStone -{ - BinarySemaphore::BinarySemaphore() : - proceed_(false) - { - } - - void BinarySemaphore::Signal() - { - //boost::mutex::scoped_lock lock(mutex_); - - proceed_ = true; - condition_.notify_one(); - } - - void BinarySemaphore::Wait() - { - boost::mutex::scoped_lock lock(mutex_); - - while (!proceed_) - { - condition_.wait(lock); - } - - proceed_ = false; - } -}
--- a/OrthancStone/Resources/Graveyard/Threading/BinarySemaphore.h Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <boost/thread/mutex.hpp> -#include <boost/thread/condition.hpp> - -namespace OrthancStone -{ - class BinarySemaphore : public boost::noncopyable - { - private: - bool proceed_; - boost::mutex mutex_; - boost::condition_variable condition_; - - public: - explicit BinarySemaphore(); - - void Signal(); - - void Wait(); - }; -}
--- a/OrthancStone/Resources/Graveyard/Threading/IThreadSafety.h Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <boost/noncopyable.hpp> - -namespace OrthancStone -{ - /** - * Dummy interface to explicitely tag the interfaces whose derived - * class must be thread-safe. The different methods of such classes - * might be simlultaneously invoked by several threads, and should - * be properly protected by mutexes. - **/ - class IThreadSafe : public boost::noncopyable - { - public: - virtual ~IThreadSafe() - { - } - }; - - - /** - * Dummy interface to explicitely tag the interfaces that are NOT - * expected to be thread-safe. The Orthanc Stone framework ensures - * that at most one method of such classes will be invoked at a - * given time. Such classes are automatically protected by the - * Orthanc Stone framework wherever required. - **/ - class IThreadUnsafe : public boost::noncopyable - { - public: - virtual ~IThreadUnsafe() - { - } - }; -}
--- a/OrthancStone/Resources/Graveyard/Threading/SdlBuffering.cpp Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "SdlBuffering.h" - -#if ORTHANC_ENABLE_SDL == 1 - -#include "../../Resources/Orthanc/Core/Logging.h" -#include "../../Resources/Orthanc/Core/OrthancException.h" - -namespace OrthancStone -{ - SdlBuffering::SdlBuffering() : - sdlSurface_(NULL), - pendingFrame_(false) - { - } - - - SdlBuffering::~SdlBuffering() - { - if (sdlSurface_) - { - SDL_FreeSurface(sdlSurface_); - } - } - - - void SdlBuffering::SetSize(unsigned int width, - unsigned int height, - IViewport& viewport) - { - boost::mutex::scoped_lock lock(mutex_); - - viewport.SetSize(width, height); - - if (offscreenSurface_.get() == NULL || - offscreenSurface_->GetWidth() != width || - offscreenSurface_->GetHeight() != height) - { - offscreenSurface_.reset(new CairoSurface(width, height)); - } - - if (onscreenSurface_.get() == NULL || - onscreenSurface_->GetWidth() != width || - onscreenSurface_->GetHeight() != height) - { - onscreenSurface_.reset(new CairoSurface(width, height)); - - // TODO Big endian? - static const uint32_t rmask = 0x00ff0000; - static const uint32_t gmask = 0x0000ff00; - static const uint32_t bmask = 0x000000ff; - - if (sdlSurface_) - { - SDL_FreeSurface(sdlSurface_); - } - - sdlSurface_ = SDL_CreateRGBSurfaceFrom(onscreenSurface_->GetBuffer(), width, height, 32, - onscreenSurface_->GetPitch(), rmask, gmask, bmask, 0); - if (!sdlSurface_) - { - LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - pendingFrame_ = false; - } - - - bool SdlBuffering::RenderOffscreen(IViewport& viewport) - { - boost::mutex::scoped_lock lock(mutex_); - - if (offscreenSurface_.get() == NULL) - { - return false; - } - - Orthanc::ImageAccessor target = offscreenSurface_->GetAccessor(); - - if (viewport.Render(target) && - !pendingFrame_) - { - pendingFrame_ = true; - return true; - } - else - { - return false; - } - } - - - void SdlBuffering::SwapToScreen(SdlWindow& window) - { - if (!pendingFrame_ || - offscreenSurface_.get() == NULL || - onscreenSurface_.get() == NULL) - { - return; - } - - { - boost::mutex::scoped_lock lock(mutex_); - onscreenSurface_->Copy(*offscreenSurface_); - } - - window.Render(sdlSurface_); - pendingFrame_ = false; - } -} - -#endif
--- a/OrthancStone/Resources/Graveyard/Threading/SdlBuffering.h Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#if ORTHANC_ENABLE_SDL == 1 - -#include "SdlWindow.h" -#include "../../Framework/Viewport/CairoSurface.h" -#include "../../Framework/Viewport/IViewport.h" - -#include <boost/thread/mutex.hpp> - -namespace OrthancStone -{ - class SdlBuffering : public boost::noncopyable - { - private: - boost::mutex mutex_; - std::unique_ptr<CairoSurface> offscreenSurface_; - std::unique_ptr<CairoSurface> onscreenSurface_; - SDL_Surface* sdlSurface_; - bool pendingFrame_; - - public: - SdlBuffering(); - - ~SdlBuffering(); - - void SetSize(unsigned int width, - unsigned int height, - IViewport& viewport); - - // Returns "true" if a new refresh of the display should be - // triggered afterwards - bool RenderOffscreen(IViewport& viewport); - - void SwapToScreen(SdlWindow& window); - }; -} - -#endif
--- a/OrthancStone/Resources/Graveyard/Threading/SharedValue.h Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <boost/noncopyable.hpp> -#include <boost/thread/mutex.hpp> - -namespace OrthancStone -{ - // A value that is protected by a mutex, in order to be shared by - // multiple threads - template <typename T> - class SharedValue : public boost::noncopyable - { - private: - boost::mutex mutex_; - T value_; - - public: - class Locker : public boost::noncopyable - { - private: - boost::mutex::scoped_lock lock_; - T& value_; - - public: - Locker(SharedValue& shared) : - lock_(shared.mutex_), - value_(shared.value_) - { - } - - T& GetValue() const - { - return value_; - } - }; - }; -}
--- a/OrthancStone/Resources/Graveyard/Toolbox/DicomDataset.cpp Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,304 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "DicomDataset.h" - -#include "../../Resources/Orthanc/Core/OrthancException.h" -#include "../../Resources/Orthanc/Core/Logging.h" -#include "../../Resources/Orthanc/Core/Toolbox.h" - -#include <boost/lexical_cast.hpp> -#include <json/value.h> -#include <json/reader.h> - -namespace OrthancStone -{ - static uint16_t GetCharValue(char c) - { - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else - return 0; - } - - - static uint16_t GetHexadecimalValue(const char* c) - { - return ((GetCharValue(c[0]) << 12) + - (GetCharValue(c[1]) << 8) + - (GetCharValue(c[2]) << 4) + - GetCharValue(c[3])); - } - - - static DicomDataset::Tag ParseTag(const std::string& tag) - { - if (tag.size() == 9 && - isxdigit(tag[0]) && - isxdigit(tag[1]) && - isxdigit(tag[2]) && - isxdigit(tag[3]) && - (tag[4] == '-' || tag[4] == ',') && - isxdigit(tag[5]) && - isxdigit(tag[6]) && - isxdigit(tag[7]) && - isxdigit(tag[8])) - { - uint16_t group = GetHexadecimalValue(tag.c_str()); - uint16_t element = GetHexadecimalValue(tag.c_str() + 5); - return std::make_pair(group, element); - } - else if (tag.size() == 8 && - isxdigit(tag[0]) && - isxdigit(tag[1]) && - isxdigit(tag[2]) && - isxdigit(tag[3]) && - isxdigit(tag[4]) && - isxdigit(tag[5]) && - isxdigit(tag[6]) && - isxdigit(tag[7])) - { - uint16_t group = GetHexadecimalValue(tag.c_str()); - uint16_t element = GetHexadecimalValue(tag.c_str() + 4); - return std::make_pair(group, element); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - void DicomDataset::Parse(const std::string& content) - { - Json::Value json; - Json::Reader reader; - if (!reader.parse(content, json)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - Parse(json); - } - - - void DicomDataset::Parse(const Json::Value& content) - { - if (content.type() != Json::objectValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - Json::Value::Members members = content.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - Tag tag = ParseTag(members[i]); - - const Json::Value& item = content[members[i]]; - - if (item.type() != Json::objectValue || - !item.isMember("Type") || - !item.isMember("Value") || - !item.isMember("Name") || - item["Type"].type() != Json::stringValue || - item["Name"].type() != Json::stringValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - if (item["Type"].asString() == "String") - { - if (item["Value"].type() == Json::stringValue) - { - values_[tag] = item["Value"].asString(); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - } - } - - - DicomDataset::DicomDataset(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instanceId) - { - std::string content; - orthanc.RestApiGet(content, "/instances/" + instanceId + "/tags"); - - Parse(content); - } - - - std::string DicomDataset::GetStringValue(const Tag& tag) const - { - Values::const_iterator it = values_.find(tag); - - if (it == values_.end()) - { - LOG(ERROR) << "Trying to access a DICOM tag that is not set in a DICOM dataset"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); - } - else - { - return it->second; - } - } - - - std::string DicomDataset::GetStringValue(const Tag& tag, - const std::string& defaultValue) const - { - Values::const_iterator it = values_.find(tag); - - if (it == values_.end()) - { - return defaultValue; - } - else - { - return it->second; - } - } - - - float DicomDataset::GetFloatValue(const Tag& tag) const - { - try - { - return boost::lexical_cast<float>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag))); - } - catch (boost::bad_lexical_cast&) - { - LOG(ERROR) << "Trying to access a DICOM tag that is not a float"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - - double DicomDataset::GetDoubleValue(const Tag& tag) const - { - try - { - return boost::lexical_cast<double>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag))); - } - catch (boost::bad_lexical_cast&) - { - LOG(ERROR) << "Trying to access a DICOM tag that is not a float"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - - int DicomDataset::GetIntegerValue(const Tag& tag) const - { - try - { - return boost::lexical_cast<int>(Orthanc::Toolbox::StripSpaces(GetStringValue(tag))); - } - catch (boost::bad_lexical_cast&) - { - LOG(ERROR) << "Trying to access a DICOM tag that is not an integer"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - - unsigned int DicomDataset::GetUnsignedIntegerValue(const Tag& tag) const - { - int v = GetIntegerValue(tag); - - if (v < 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - return static_cast<unsigned int>(v); - } - } - - - void DicomDataset::GetVectorValue(Vector& vector, - const Tag& tag) const - { - if (!GeometryToolbox::ParseVector(vector, Orthanc::Toolbox::StripSpaces(GetStringValue(tag)))) - { - LOG(ERROR) << "Trying to access a DICOM tag that is not a vector"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - - void DicomDataset::GetVectorValue(Vector& vector, - const Tag& tag, - size_t expectedSize) const - { - GetVectorValue(vector, tag); - - if (vector.size() != expectedSize) - { - LOG(ERROR) << "A vector in a DICOM tag has a bad size"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - - void DicomDataset::Print() const - { - for (Values::const_iterator it = values_.begin(); it != values_.end(); ++it) - { - printf("%04x,%04x = [%s]\n", it->first.first, it->first.second, it->second.c_str()); - } - printf("\n"); - } - - - bool DicomDataset::IsGrayscale() const - { - std::string photometric = Orthanc::Toolbox::StripSpaces(GetStringValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION)); - - return (photometric == "MONOCHROME1" || - photometric == "MONOCHROME2"); - } - - - void DicomDataset::GetPixelSpacing(double& spacingX, - double& spacingY) const - { - if (HasTag(DICOM_TAG_PIXEL_SPACING)) - { - Vector spacing; - GetVectorValue(spacing, DICOM_TAG_PIXEL_SPACING, 2); - spacingX = spacing[0]; - spacingY = spacing[1]; - } - else - { - spacingX = 1.0; - spacingY = 1.0; - } - } -}
--- a/OrthancStone/Resources/Graveyard/Toolbox/DicomDataset.h Wed Oct 21 18:00:34 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../../Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h" - -#include <map> -#include <stdint.h> -#include <json/value.h> - -namespace OrthancStone -{ - // This class is NOT thread-safe - // This is a lightweight alternative to Orthanc::DicomMap - class DicomDataset : public boost::noncopyable - { - public: - typedef std::pair<uint16_t, uint16_t> Tag; - - private: - typedef std::map<Tag, std::string> Values; - - Values values_; - - void Parse(const std::string& content); - - void Parse(const Json::Value& content); - - public: - DicomDataset(const std::string& content) - { - Parse(content); - } - - DicomDataset(const Json::Value& content) - { - Parse(content); - } - - DicomDataset(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instanceId); - - bool HasTag(const Tag& tag) const - { - return values_.find(tag) != values_.end(); - } - - std::string GetStringValue(const Tag& tag) const; - - std::string GetStringValue(const Tag& tag, - const std::string& defaultValue) const; - - float GetFloatValue(const Tag& tag) const; - - double GetDoubleValue(const Tag& tag) const; - - int GetIntegerValue(const Tag& tag) const; - - unsigned int GetUnsignedIntegerValue(const Tag& tag) const; - - void GetVectorValue(Vector& vector, - const Tag& tag, - size_t expectedSize) const; - - void GetVectorValue(Vector& vector, - const Tag& tag) const; - - void Print() const; - - bool IsGrayscale() const; - - void GetPixelSpacing(double& spacingX, - double& spacingY) const; - }; - - - static const DicomDataset::Tag DICOM_TAG_COLUMNS(0x0028, 0x0011); - static const DicomDataset::Tag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037); - static const DicomDataset::Tag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032); - static const DicomDataset::Tag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008); - static const DicomDataset::Tag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103); - static const DicomDataset::Tag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030); - static const DicomDataset::Tag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052); - static const DicomDataset::Tag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053); - static const DicomDataset::Tag DICOM_TAG_ROWS(0x0028, 0x0010); - static const DicomDataset::Tag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050); - static const DicomDataset::Tag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050); - static const DicomDataset::Tag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051); - static const DicomDataset::Tag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004); -}