Mercurial > hg > orthanc-stone
changeset 1354:c0e4eb14c912 broker
SingleFrameViewer WASM working OK
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Wed, 15 Apr 2020 12:59:15 +0200 |
parents | af65bce18951 |
children | 4971b1c5dfa6 |
files | Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h Samples/WebAssembly/SingleFrameViewer/WasmWrapper.js Samples/WebAssembly/SingleFrameViewer/index.html |
diffstat | 5 files changed, 740 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt Wed Apr 15 12:59:15 2020 +0200 @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 2.8.3) + +# Configuration of the Emscripten compiler for WebAssembly target +# --------------------------------------------------------------- +set(USE_WASM ON CACHE BOOL "") +set(ORTHANC_FRAMEWORK_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../orthanc CACHE STRING "") +set(STONE_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../orthanc-stone) + +set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") + +set(WASM_FLAGS "-s WASM=1 -s FETCH=1") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") +endif() + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") + +# Stone of Orthanc configuration +# --------------------------------------------------------------- +set(ALLOW_DOWNLOADS ON) +set(ORTHANC_FRAMEWORK_SOURCE "path") + +include(${STONE_ROOT}/Resources/CMake/OrthancStoneParameters.cmake) + +SET(ENABLE_DCMTK ON) +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ENABLE_WASM ON) +SET(ORTHANC_SANDBOXED ON) + +# this will set up the build system for Stone of Orthanc and will +# populate the ORTHANC_STONE_SOURCES CMake variable +include(${STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) + +include_directories(${STONE_ROOT}) + +# Define the WASM module +# --------------------------------------------------------------- +add_executable(SingleFrameViewerWasm + test.cpp + ${ORTHANC_STONE_SOURCES} + ) + +# Declare installation files for the module +# --------------------------------------------------------------- +install( + TARGETS SingleFrameViewerWasm + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} + ) + +# Declare installation files for the companion files (web scaffolding) +# please note that ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.js +# (the generated JS loader for the WASM module) is handled by the `install1` +# section above +# --------------------------------------------------------------- +install( + FILES + ${CMAKE_SOURCE_DIR}/SingleFrameViewerApp.js + ${CMAKE_SOURCE_DIR}/index.html + ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.wasm + ${CMAKE_SOURCE_DIR}/WasmWrapper.js + DESTINATION ${CMAKE_INSTALL_PREFIX} + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js Wed Apr 15 12:59:15 2020 +0200 @@ -0,0 +1,89 @@ + +// This object wraps the functions exposed by the wasm module + +const WasmModuleWrapper = function() { + this._InitializeViewport = undefined; + this._LoadOrthanc = undefined; + this._LoadDicomWeb = undefined; +}; + +WasmModuleWrapper.prototype.Setup = function(Module) { + this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]); + this._LoadOrthanc = Module.cwrap('LoadOrthanc', null, [ 'string', 'int' ]); + this._LoadDicomWeb = Module.cwrap('LoadDicomWeb', null, [ 'string', 'string', 'string', 'string', 'int' ]); +}; + +WasmModuleWrapper.prototype.InitializeViewport = function(canvasId) { + this._InitializeViewport(canvasId); +}; + +WasmModuleWrapper.prototype.LoadOrthanc = function(instance, frame) { + this._LoadOrthanc(instance, frame); +}; + +WasmModuleWrapper.prototype.LoadDicomWeb = function(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) { + this._LoadDicomWeb(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame); +}; + +var moduleWrapper = new WasmModuleWrapper(); + +$(document).ready(function() { + + window.addEventListener('StoneInitialized', function() { + stone.Setup(Module); + console.warn('Native Stone properly intialized'); + + stone.InitializeViewport('viewport'); + }); + + window.addEventListener('StoneException', function() { + alert('Exception caught in Stone'); + }); + + var scriptSource; + + if ('WebAssembly' in window) { + console.warn('Loading WebAssembly'); + scriptSource = 'SingleFrameViewerWasm.js'; + } else { + console.error('Your browser does not support WebAssembly!'); + } + + // Option 1: Loading script using plain HTML + + /* + var script = document.createElement('script'); + script.src = scriptSource; + script.type = 'text/javascript'; + document.body.appendChild(script); + */ + + // Option 2: Loading script using AJAX (gives the opportunity to + // report explicit errors) + + axios.get(scriptSource) + .then(function (response) { + var script = document.createElement('script'); + script.innerHTML = response.data; + script.type = 'text/javascript'; + document.body.appendChild(script); + }) + .catch(function (error) { + alert('Cannot load the WebAssembly framework'); + }); +}); + + +$('#orthancLoad').click(function() { + stone.LoadOrthanc($('#orthancInstance').val(), + $('#orthancFrame').val()); +}); + + +$('#dicomWebLoad').click(function() { + stone.LoadDicomWeb($('#dicomWebServer').val(), + $('#dicomWebStudy').val(), + $('#dicomWebSeries').val(), + $('#dicomWebInstance').val(), + $('#dicomWebFrame').val()); +});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h Wed Apr 15 12:59:15 2020 +0200 @@ -0,0 +1,483 @@ +#pragma once + +#include <Framework/Viewport/IViewport.h> +#include <Framework/Loaders/DicomResourcesLoader.h> +#include <Framework/Loaders/ILoadersContext.h> +#include <Framework/Loaders/SeriesFramesLoader.h> +#include <Framework/Loaders/SeriesThumbnailsLoader.h> + +#include <boost/make_shared.hpp> + + +namespace OrthancStone +{ + class Application : public ObserverBase<Application> + { + private: + ILoadersContext& context_; + boost::shared_ptr<IViewport> viewport_; + boost::shared_ptr<DicomResourcesLoader> dicomLoader_; + boost::shared_ptr<SeriesFramesLoader> framesLoader_; + + Application(ILoadersContext& context, + boost::shared_ptr<IViewport> viewport) : + context_(context), + viewport_(viewport) + { + } + + void Handle(const SeriesFramesLoader::FrameLoadedMessage& message) + { + LOG(INFO) << "Frame decoded! " + << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight() + << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat()); + + std::auto_ptr<TextureBaseSceneLayer> layer( + message.GetInstanceParameters().CreateTexture(message.GetImage())); + layer->SetLinearInterpolation(true); + + { + std::auto_ptr<IViewport::ILock> lock(viewport_->Lock()); + lock->GetController().GetScene().SetLayer(0, layer.release()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + } + + void Handle(const DicomResourcesLoader::SuccessMessage& message) + { + if (message.GetResources()->GetSize() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + //message.GetResources()->GetResource(0).Print(stdout); + + { + std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock()); + SeriesFramesLoader::Factory f(*message.GetResources()); + + framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(f.Create(*lock)); + Register<SeriesFramesLoader::FrameLoadedMessage>(*framesLoader_, &Application::Handle); + + assert(message.HasUserPayload()); + const Orthanc::SingleValueObject<unsigned int>& payload = + dynamic_cast<const Orthanc::SingleValueObject<unsigned int>&>(message.GetUserPayload()); + + LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue(); + framesLoader_->ScheduleLoadFrame( + 0, message.GetDicomSource(), payload.GetValue(), + message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */, + NULL); + } + } + + public: + static boost::shared_ptr<Application> Create(ILoadersContext& context, + boost::shared_ptr<IViewport> viewport) + { + boost::shared_ptr<Application> application(new Application(context, viewport)); + + { + std::auto_ptr<ILoadersContext::ILock> lock(context.Lock()); + DicomResourcesLoader::Factory f; + application->dicomLoader_ = boost::dynamic_pointer_cast<DicomResourcesLoader>(f.Create(*lock)); + } + + application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &Application::Handle); + + return application; + } + + void LoadOrthancFrame(const DicomSource& source, + const std::string& instanceId, + unsigned int frame) + { + std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock()); + + dicomLoader_->ScheduleLoadOrthancResource( + boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), + 0, source, Orthanc::ResourceType_Instance, instanceId, + new Orthanc::SingleValueObject<unsigned int>(frame)); + } + + void LoadDicomWebFrame(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + unsigned int frame) + { + std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock()); + + // We first must load the "/metadata" to know the number of frames + dicomLoader_->ScheduleGetDicomWeb( + boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, + "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", + new Orthanc::SingleValueObject<unsigned int>(frame)); + } + + void FitContent() + { + std::auto_ptr<IViewport::ILock> lock(viewport_->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + }; + + + + class IWebViewerLoadersObserver : public boost::noncopyable + { + public: + virtual ~IWebViewerLoadersObserver() + { + } + + virtual void SignalSeriesUpdated(LoadedDicomResources& series) = 0; + + virtual void SignalThumbnailLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + SeriesThumbnailType type) = 0; + }; + + + class WebViewerLoaders : public ObserverBase<WebViewerLoaders> + { + private: + static const int PRIORITY_ADD_RESOURCES = 0; + static const int PRIORITY_THUMBNAILS = OracleScheduler::PRIORITY_LOW + 100; + + enum Type + { + Type_Orthanc = 1, + Type_DicomWeb = 2 + }; + + ILoadersContext& context_; + std::auto_ptr<IWebViewerLoadersObserver> observer_; + bool loadThumbnails_; + DicomSource source_; + std::set<std::string> scheduledSeries_; + std::set<std::string> scheduledThumbnails_; + std::set<std::string> scheduledStudies_; + boost::shared_ptr<LoadedDicomResources> loadedSeries_; + boost::shared_ptr<LoadedDicomResources> loadedStudies_; + boost::shared_ptr<DicomResourcesLoader> resourcesLoader_; + boost::shared_ptr<SeriesThumbnailsLoader> thumbnailsLoader_; + + WebViewerLoaders(ILoadersContext& context, + IWebViewerLoadersObserver* observer) : + context_(context), + observer_(observer), + loadThumbnails_(false) + { + loadedSeries_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); + loadedStudies_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID); + } + + static Orthanc::IDynamicObject* CreatePayload(Type type) + { + return new Orthanc::SingleValueObject<Type>(type); + } + + void HandleThumbnail(const SeriesThumbnailsLoader::ThumbnailLoadedMessage& message) + { + if (observer_.get() != NULL) + { + observer_->SignalThumbnailLoaded(message.GetStudyInstanceUid(), + message.GetSeriesInstanceUid(), + message.GetType()); + } + } + + void HandleLoadedResources(const DicomResourcesLoader::SuccessMessage& message) + { + LoadedDicomResources series(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); + + switch (dynamic_cast<const Orthanc::SingleValueObject<Type>&>(message.GetUserPayload()).GetValue()) + { + case Type_DicomWeb: + { + for (size_t i = 0; i < loadedSeries_->GetSize(); i++) + { + std::string study; + if (loadedSeries_->GetResource(i).LookupStringValue( + study, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && + loadedStudies_->HasResource(study)) + { + Orthanc::DicomMap m; + m.Assign(loadedSeries_->GetResource(i)); + loadedStudies_->MergeResource(m, study); + series.AddResource(m); + } + } + + break; + } + + case Type_Orthanc: + { + for (size_t i = 0; i < message.GetResources()->GetSize(); i++) + { + series.AddResource(message.GetResources()->GetResource(i)); + } + + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + if (loadThumbnails_ && + (!source_.IsDicomWeb() || + source_.HasDicomWebRendered())) + { + for (size_t i = 0; i < series.GetSize(); i++) + { + std::string patientId, studyInstanceUid, seriesInstanceUid; + if (series.GetResource(i).LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) && + series.GetResource(i).LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && + series.GetResource(i).LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) && + scheduledThumbnails_.find(seriesInstanceUid) == scheduledThumbnails_.end()) + { + scheduledThumbnails_.insert(seriesInstanceUid); + thumbnailsLoader_->ScheduleLoadThumbnail(source_, patientId, studyInstanceUid, seriesInstanceUid); + } + } + } + + if (observer_.get() != NULL && + series.GetSize() > 0) + { + observer_->SignalSeriesUpdated(series); + } + } + + void HandleOrthancRestApi(const OrthancRestApiCommand::SuccessMessage& message) + { + Json::Value body; + message.ParseJsonBody(body); + + if (body.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + else + { + for (Json::Value::ArrayIndex i = 0; i < body.size(); i++) + { + if (body[i].type() == Json::stringValue) + { + AddOrthancSeries(body[i].asString()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + } + } + + public: + static boost::shared_ptr<WebViewerLoaders> Create(ILoadersContext& context, + const DicomSource& source, + bool loadThumbnails, + IWebViewerLoadersObserver* observer) + { + boost::shared_ptr<WebViewerLoaders> application(new WebViewerLoaders(context, observer)); + application->source_ = source; + application->loadThumbnails_ = loadThumbnails; + + { + std::auto_ptr<ILoadersContext::ILock> lock(context.Lock()); + + { + DicomResourcesLoader::Factory f; + application->resourcesLoader_ = boost::dynamic_pointer_cast<DicomResourcesLoader>(f.Create(*lock)); + } + + { + SeriesThumbnailsLoader::Factory f; + f.SetPriority(PRIORITY_THUMBNAILS); + application->thumbnailsLoader_ = boost::dynamic_pointer_cast<SeriesThumbnailsLoader>(f.Create(*lock)); + } + + application->Register<OrthancRestApiCommand::SuccessMessage>( + lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi); + + application->Register<DicomResourcesLoader::SuccessMessage>( + *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources); + + application->Register<SeriesThumbnailsLoader::ThumbnailLoadedMessage>( + *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail); + + lock->AddLoader(application); + } + + return application; + } + + void AddDicomAllSeries() + { + std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock()); + + if (source_.IsDicomWeb()) + { + resourcesLoader_->ScheduleGetDicomWeb(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + "/series", CreatePayload(Type_DicomWeb)); + resourcesLoader_->ScheduleGetDicomWeb(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, + "/studies", CreatePayload(Type_DicomWeb)); + } + else if (source_.IsOrthanc()) + { + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Get); + command->SetUri("/series"); + lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + void AddDicomStudy(const std::string& studyInstanceUid) + { + // Avoid adding twice the same study + if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end()) + { + scheduledStudies_.insert(studyInstanceUid); + + if (source_.IsDicomWeb()) + { + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + + std::set<Orthanc::DicomTag> tags; + + { + std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock()); + + resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb)); + + resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb)); + } + } + else if (source_.IsOrthanc()) + { + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetUri("/tools/find"); + + Json::Value body; + body["Level"] = "Series"; + body["Query"] = Json::objectValue; + body["Query"]["StudyInstanceUID"] = studyInstanceUid; + command->SetBody(body); + + { + std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + + void AddDicomSeries(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) + { + std::set<Orthanc::DicomTag> tags; + + std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock()); + + if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end()) + { + scheduledStudies_.insert(studyInstanceUid); + + if (source_.IsDicomWeb()) + { + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + + resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb)); + } + } + + if (scheduledSeries_.find(seriesInstanceUid) == scheduledSeries_.end()) + { + scheduledSeries_.insert(seriesInstanceUid); + + if (source_.IsDicomWeb()) + { + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false); + + resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb)); + } + else if (source_.IsOrthanc()) + { + std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetUri("/tools/find"); + + Json::Value body; + body["Level"] = "Series"; + body["Query"] = Json::objectValue; + body["Query"]["StudyInstanceUID"] = studyInstanceUid; + body["Query"]["SeriesInstanceUID"] = seriesInstanceUid; + command->SetBody(body); + + lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + + void AddOrthancStudy(const std::string& orthancId) + { + if (source_.IsOrthanc()) + { + std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock()); + resourcesLoader_->ScheduleLoadOrthancResources( + loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Study, orthancId, Orthanc::ResourceType_Series, + CreatePayload(Type_Orthanc)); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, + "Only applicable to Orthanc DICOM sources"); + } + } + + void AddOrthancSeries(const std::string& orthancId) + { + if (source_.IsOrthanc()) + { + std::auto_ptr<ILoadersContext::ILock> lock(context_.Lock()); + resourcesLoader_->ScheduleLoadOrthancResource( + loadedSeries_, PRIORITY_ADD_RESOURCES, + source_, Orthanc::ResourceType_Series, orthancId, + CreatePayload(Type_Orthanc)); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, + "Only applicable to Orthanc DICOM sources"); + } + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/WasmWrapper.js Wed Apr 15 12:59:15 2020 +0200 @@ -0,0 +1,27 @@ + +const Stone = function() { + this._InitializeViewport = undefined; + this._LoadOrthanc = undefined; + this._LoadDicomWeb = undefined; +}; + +Stone.prototype.Setup = function(Module) { + this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]); + this._LoadOrthanc = Module.cwrap('LoadOrthanc', null, [ 'string', 'int' ]); + this._LoadDicomWeb = Module.cwrap('LoadDicomWeb', null, [ 'string', 'string', 'string', 'string', 'int' ]); +}; + +Stone.prototype.InitializeViewport = function(canvasId) { + this._InitializeViewport(canvasId); +}; + +Stone.prototype.LoadOrthanc = function(instance, frame) { + this._LoadOrthanc(instance, frame); +}; + +Stone.prototype.LoadDicomWeb = function(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) { + this._LoadDicomWeb(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame); +}; + +var stone = new Stone(); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/SingleFrameViewer/index.html Wed Apr 15 12:59:15 2020 +0200 @@ -0,0 +1,71 @@ +<!doctype html> +<html lang="en"> + <head> + <title>Osimis' Web Viewer</title> + <meta charset="utf-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> + <link rel="icon" href="data:;base64,iVBORw0KGgo="> + + <style> + canvas { + background-color: green; + width : 100%; + height : 512px; + } + </style> + </head> + <body> + <h1>Viewport</h1> + + <canvas id="viewport" > + </canvas> + + <h1>Load from Orthanc</h1> + <p> + Orthanc instance: <input type="text" id="orthancInstance" size="80" + value="5eb2dd5f-3fca21a8-fa7565fd-63e112ae-344830a4"> + <!-- Orthanc instance: <input type="text" id="orthancInstance" size="80" + value="61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b"> --> + <!-- Orthanc instance: <input type="text" id="orthancInstance" size="80" + value="8dddbd75-f4095768-6e66f851-22305751-18782bdd"> --> + </p> + <p> + Frame number: <input type="text" id="orthancFrame" value="0"> + </p> + <p> + <button id="orthancLoad">Load</button> + </p> + + <h1>Load from DICOMweb</h1> + <p> + Server name in Orthanc: <input type="text" id="dicomWebServer" value="self"> + </p> + <p> + Study instance UID: <input type="text" id="dicomWebStudy" size="80" + value="1.2.840.113543.6.6.4.7.64067529866380271256212683512383713111129"> + </p> + <p> + Series instance UID: <input type="text" id="dicomWebSeries" size="80" + value="1.2.840.113543.6.6.4.7.63556916880112768082712975118701689357177"> + </p> + <p> + SOP instance UID: <input type="text" id="dicomWebInstance" size="80" + value="1.2.840.113543.6.6.4.7.64234348190163144631511103849051737563212"> + </p> + <p> + Frame number: <input type="text" id="dicomWebFrame" value="0"> + </p> + <p> + <button id="dicomWebLoad">Load</button> + </p> + + <script src="https://code.jquery.com/jquery-3.4.1.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script> + + <script src="WasmWrapper.js"></script> + <script src="SingleFrameViewerApp.js"></script> + </body> +</html>