Mercurial > hg > orthanc-stone
changeset 2203:dcfabb36dc21
introducing a plugin system to refactor deep-learning
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sat, 19 Apr 2025 14:40:38 +0200 (6 weeks ago) |
parents | be298b7f5469 |
children | e1613509a939 b36f7b6eec39 c1cc403eae8e |
files | Applications/StoneWebViewer/WebAssembly/IStoneWebViewerContext.h Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp |
diffstat | 3 files changed, 201 insertions(+), 67 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebAssembly/IStoneWebViewerContext.h Sat Apr 19 14:40:38 2025 +0200 @@ -0,0 +1,106 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, 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 "../../../OrthancStone/Sources/Scene2D/ISceneLayer.h" +#include "../../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.h" + +#include <Images/ImageAccessor.h> + + +#define DISPATCH_JAVASCRIPT_EVENT(name) \ + EM_ASM( \ + const customEvent = document.createEvent("CustomEvent"); \ + customEvent.initCustomEvent(name, false, false, undefined); \ + window.dispatchEvent(customEvent); \ + ); + +#define EXTERN_CATCH_EXCEPTIONS \ + catch (Orthanc::OrthancException& e) \ + { \ + LOG(ERROR) << "OrthancException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (OrthancStone::StoneException& e) \ + { \ + LOG(ERROR) << "StoneException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (std::exception& e) \ + { \ + LOG(ERROR) << "Runtime error: " << e.what(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (...) \ + { \ + LOG(ERROR) << "Native exception"; \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } + + +// WARNING: This class can be shared by multiple viewports +class ILayerSource : public boost::noncopyable +{ +public: + virtual ~ILayerSource() + { + } + + virtual int GetDepth() const = 0; + + virtual OrthancStone::ISceneLayer* Create(const Orthanc::ImageAccessor& frame, + const OrthancStone::DicomInstanceParameters& instance, + unsigned int frameNumber, + double pixelSpacingX, + double pixelSpacingY, + const OrthancStone::CoordinateSystem3D& plane) = 0; +}; + + +class IStoneWebViewerContext : public boost::noncopyable +{ +public: + virtual ~IStoneWebViewerContext() + { + } + + virtual void RedrawAllViewports() = 0; + + // WARNING: The ImageAccessor will become invalid once leaving the + // JavaScript callback, do not keep a reference! + virtual bool GetSelectedFrame(Orthanc::ImageAccessor& target /* out */, + std::string& sopInstanceUid /* out */, + unsigned int& frameNumber /* out */, + const std::string& canvas /* in */) = 0; +}; + + +class IStoneWebViewerPlugin : public boost::noncopyable +{ +public: + virtual ~IStoneWebViewerPlugin() + { + } + + virtual ILayerSource& GetLayerSource() = 0; +};
--- a/Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py Sat Apr 19 11:47:04 2025 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py Sat Apr 19 14:40:38 2025 +0200 @@ -45,8 +45,8 @@ parser.add_argument('--libclang', default = '', help = 'manually provides the path to the libclang shared library') -parser.add_argument('source', - help = 'Input C++ file') +parser.add_argument('sources', nargs='+', + help = 'Input C++ files') args = parser.parse_args() @@ -57,13 +57,6 @@ index = clang.cindex.Index.create() -# PARSE_SKIP_FUNCTION_BODIES prevents clang from failing because of -# undefined types, which prevents compilation of functions -tu = index.parse(args.source, - [ '-DEMSCRIPTEN_KEEPALIVE=__attribute__((annotate("WebAssembly")))', - '-DSTONE_WEB_VIEWER_EXPORT=__attribute__((annotate("WebAssembly")))'], - options = clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES) - TEMPLATE = ''' @@ -197,8 +190,15 @@ for child in node.get_children(): Explore(child) -Explore(tu.cursor) +for source in args.sources: + # PARSE_SKIP_FUNCTION_BODIES prevents clang from failing because of + # undefined types, which prevents compilation of functions + tu = index.parse(source, + [ '-DEMSCRIPTEN_KEEPALIVE=__attribute__((annotate("WebAssembly")))', + '-DSTONE_WEB_VIEWER_EXPORT=__attribute__((annotate("WebAssembly")))'], + options = clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES) + Explore(tu.cursor) print(pystache.render(TEMPLATE, {
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Sat Apr 19 11:47:04 2025 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Sat Apr 19 14:40:38 2025 +0200 @@ -20,41 +20,12 @@ **/ +#include "IStoneWebViewerContext.h" + #include <EmbeddedResources.h> #include <emscripten.h> -#define DISPATCH_JAVASCRIPT_EVENT(name) \ - EM_ASM( \ - const customEvent = document.createEvent("CustomEvent"); \ - customEvent.initCustomEvent(name, false, false, undefined); \ - window.dispatchEvent(customEvent); \ - ); - - -#define EXTERN_CATCH_EXCEPTIONS \ - catch (Orthanc::OrthancException& e) \ - { \ - LOG(ERROR) << "OrthancException: " << e.What(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (OrthancStone::StoneException& e) \ - { \ - LOG(ERROR) << "StoneException: " << e.What(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (std::exception& e) \ - { \ - LOG(ERROR) << "Runtime error: " << e.what(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (...) \ - { \ - LOG(ERROR) << "Native exception"; \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } - - // Orthanc framework includes #include <Cache/MemoryObjectCache.h> #include <DicomFormat/DicomArray.h> @@ -1789,25 +1760,6 @@ -// WARNING: This class can be shared by multiple viewports -class ILayerSource : public boost::noncopyable -{ -public: - virtual ~ILayerSource() - { - } - - virtual int GetDepth() const = 0; - - virtual OrthancStone::ISceneLayer* Create(const Orthanc::ImageAccessor& frame, - const OrthancStone::DicomInstanceParameters& instance, - unsigned int frameNumber, - double pixelSpacingX, - double pixelSpacingY, - const OrthancStone::CoordinateSystem3D& plane) = 0; -}; - - class LayersHolder : public boost::noncopyable { private: @@ -3742,6 +3694,25 @@ } + bool GetCurrentFrame(std::string& sopInstanceUid /* out */, + unsigned int& frameNumber /* out */) const + { + if (cursor_.get() != NULL && + frames_.get() != NULL) + { + const size_t cursorIndex = cursor_->GetCurrentIndex(); + const OrthancStone::DicomInstanceParameters& instance = frames_->GetInstanceOfFrame(cursorIndex); + sopInstanceUid = instance.GetSopInstanceUid(); + frameNumber = frames_->GetFrameNumberInInstance(cursorIndex); + return true; + } + else + { + return false; + } + } + + void SignalSynchronizedBrowsing() { if (synchronizationEnabled_ && @@ -4238,6 +4209,15 @@ } +typedef IStoneWebViewerPlugin* (*PluginInitializer) (IStoneWebViewerContext&); + +static const PluginInitializer pluginsInitializers_[] = { + NULL +}; + +static std::list< boost::shared_ptr<IStoneWebViewerPlugin> > plugins_; + + static boost::shared_ptr<ViewerViewport> GetViewport(const std::string& canvas) { Viewports::iterator found = allViewports_.find(canvas); @@ -4250,6 +4230,12 @@ viewport->AddLayerSource(*overlayLayerSource_); viewport->AddLayerSource(*osiriXLayerSource_); viewport->AddLayerSource(*orientationMarkersSource_); + + for (std::list< boost::shared_ptr<IStoneWebViewerPlugin> >::iterator it = plugins_.begin(); it != plugins_.end(); ++it) + { + viewport->AddLayerSource((*it)->GetLayerSource()); + } + allViewports_[canvas] = viewport; return viewport; } @@ -4260,15 +4246,53 @@ } - -typedef void (*PluginInitializer) (); - -static const PluginInitializer pluginsInitializers_[] = { - NULL +class StoneWebViewerContext : public IStoneWebViewerContext +{ +public: + static StoneWebViewerContext& GetInstance() + { + static StoneWebViewerContext instance; + return instance; + } + + virtual void RedrawAllViewports() ORTHANC_OVERRIDE + { + for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) + { + assert(it->second != NULL); + it->second->Redraw(); + } + } + + virtual bool GetSelectedFrame(Orthanc::ImageAccessor& target /* out */, + std::string& sopInstanceUid /* out */, + unsigned int& frameNumber /* out */, + const std::string& canvas /* in */) ORTHANC_OVERRIDE + { + boost::shared_ptr<ViewerViewport> viewport = GetViewport(canvas); + + if (viewport->GetCurrentFrame(sopInstanceUid, frameNumber)) + { + FramesCache::Accessor accessor(*framesCache_, sopInstanceUid, frameNumber); + if (accessor.IsValid()) + { + accessor.GetImage().GetReadOnlyAccessor(target); + return true; + } + else + { + return false; + } + } + else + { + LOG(WARNING) << "No active frame"; + return false; + } + } }; - extern "C" { int main(int argc, char const *argv[]) @@ -4291,7 +4315,11 @@ for (size_t i = 0; pluginsInitializers_[i] != NULL; i++) { - pluginsInitializers_[i] (); + std::unique_ptr<IStoneWebViewerPlugin> plugin(pluginsInitializers_[i] (StoneWebViewerContext::GetInstance())); + if (plugin.get() != NULL) + { + plugins_.push_back(boost::shared_ptr<IStoneWebViewerPlugin>(plugin.release())); + } } DISPATCH_JAVASCRIPT_EVENT("StoneInitialized");