# HG changeset patch # User Benjamin Golinvaux # Date 1586948355 -7200 # Node ID c0e4eb14c912bd2b89a16e009eeb73a1058a7e14 # Parent af65bce1895170f05025e5103086aa79ddf2c270 SingleFrameViewer WASM working OK diff -r af65bce18951 -r c0e4eb14c912 Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt --- /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} + ) diff -r af65bce18951 -r c0e4eb14c912 Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js --- /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()); +}); diff -r af65bce18951 -r c0e4eb14c912 Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h --- /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 +#include +#include +#include +#include + +#include + + +namespace OrthancStone +{ + class Application : public ObserverBase + { + private: + ILoadersContext& context_; + boost::shared_ptr viewport_; + boost::shared_ptr dicomLoader_; + boost::shared_ptr framesLoader_; + + Application(ILoadersContext& context, + boost::shared_ptr 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 layer( + message.GetInstanceParameters().CreateTexture(message.GetImage())); + layer->SetLinearInterpolation(true); + + { + std::auto_ptr 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 lock(context_.Lock()); + SeriesFramesLoader::Factory f(*message.GetResources()); + + framesLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + Register(*framesLoader_, &Application::Handle); + + assert(message.HasUserPayload()); + const Orthanc::SingleValueObject& payload = + dynamic_cast&>(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 Create(ILoadersContext& context, + boost::shared_ptr viewport) + { + boost::shared_ptr application(new Application(context, viewport)); + + { + std::auto_ptr lock(context.Lock()); + DicomResourcesLoader::Factory f; + application->dicomLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + } + + application->Register(*application->dicomLoader_, &Application::Handle); + + return application; + } + + void LoadOrthancFrame(const DicomSource& source, + const std::string& instanceId, + unsigned int frame) + { + std::auto_ptr lock(context_.Lock()); + + dicomLoader_->ScheduleLoadOrthancResource( + boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), + 0, source, Orthanc::ResourceType_Instance, instanceId, + new Orthanc::SingleValueObject(frame)); + } + + void LoadDicomWebFrame(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + unsigned int frame) + { + std::auto_ptr lock(context_.Lock()); + + // We first must load the "/metadata" to know the number of frames + dicomLoader_->ScheduleGetDicomWeb( + boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, + "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", + new Orthanc::SingleValueObject(frame)); + } + + void FitContent() + { + std::auto_ptr 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 + { + 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 observer_; + bool loadThumbnails_; + DicomSource source_; + std::set scheduledSeries_; + std::set scheduledThumbnails_; + std::set scheduledStudies_; + boost::shared_ptr loadedSeries_; + boost::shared_ptr loadedStudies_; + boost::shared_ptr resourcesLoader_; + boost::shared_ptr thumbnailsLoader_; + + WebViewerLoaders(ILoadersContext& context, + IWebViewerLoadersObserver* observer) : + context_(context), + observer_(observer), + loadThumbnails_(false) + { + loadedSeries_ = boost::make_shared(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); + loadedStudies_ = boost::make_shared(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID); + } + + static Orthanc::IDynamicObject* CreatePayload(Type type) + { + return new Orthanc::SingleValueObject(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&>(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 Create(ILoadersContext& context, + const DicomSource& source, + bool loadThumbnails, + IWebViewerLoadersObserver* observer) + { + boost::shared_ptr application(new WebViewerLoaders(context, observer)); + application->source_ = source; + application->loadThumbnails_ = loadThumbnails; + + { + std::auto_ptr lock(context.Lock()); + + { + DicomResourcesLoader::Factory f; + application->resourcesLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + } + + { + SeriesThumbnailsLoader::Factory f; + f.SetPriority(PRIORITY_THUMBNAILS); + application->thumbnailsLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + } + + application->Register( + lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi); + + application->Register( + *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources); + + application->Register( + *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail); + + lock->AddLoader(application); + } + + return application; + } + + void AddDicomAllSeries() + { + std::auto_ptr 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 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 tags; + + { + std::auto_ptr 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 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 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 tags; + + std::auto_ptr 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 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 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 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"); + } + } + }; +} diff -r af65bce18951 -r c0e4eb14c912 Samples/WebAssembly/SingleFrameViewer/WasmWrapper.js --- /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(); + diff -r af65bce18951 -r c0e4eb14c912 Samples/WebAssembly/SingleFrameViewer/index.html --- /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 @@ + + + + Osimis' Web Viewer + + + + + + + + + + +

Viewport

+ + + + +

Load from Orthanc

+

+ Orthanc instance: + + +

+

+ Frame number: +

+

+ +

+ +

Load from DICOMweb

+

+ Server name in Orthanc: +

+

+ Study instance UID: +

+

+ Series instance UID: +

+

+ SOP instance UID: +

+

+ Frame number: +

+

+ +

+ + + + + + + +