# HG changeset patch # User Sebastien Jodogne # Date 1606747000 -3600 # Node ID 2931f5e15320b5ac16c3a8bd74a34cf72eb21ddd # Parent eb59fbee071e70dce09940570746d7de8026e850 download study from Stone Web viewer diff -r eb59fbee071e -r 2931f5e15320 Applications/StoneWebViewer/NOTES.txt --- a/Applications/StoneWebViewer/NOTES.txt Sat Nov 28 16:16:24 2020 +0100 +++ b/Applications/StoneWebViewer/NOTES.txt Mon Nov 30 15:36:40 2020 +0100 @@ -12,6 +12,10 @@ background. +- Contrarily to the Osimis Web viewer, the Stone Web viewer doesn't + currently support annotations, and will not support Live Share. + + - The Stone Web viewer uses the DICOM identifiers. The Osimis Web viewer the Orthanc identifiers. https://book.orthanc-server.com/faq/orthanc-ids.html diff -r eb59fbee071e -r 2931f5e15320 Applications/StoneWebViewer/Plugin/Plugin.cpp --- a/Applications/StoneWebViewer/Plugin/Plugin.cpp Sat Nov 28 16:16:24 2020 +0100 +++ b/Applications/StoneWebViewer/Plugin/Plugin.cpp Mon Nov 30 15:36:40 2020 +0100 @@ -174,28 +174,38 @@ { static const char* CONFIG_SECTION = "StoneWebViewer"; - std::string config; + Json::Value config = Json::objectValue; OrthancPlugins::OrthancConfiguration orthanc; if (orthanc.IsSection(CONFIG_SECTION)) { OrthancPlugins::OrthancConfiguration section(false); orthanc.GetSection(section, CONFIG_SECTION); - - Json::Value wrapper = Json::objectValue; - wrapper[CONFIG_SECTION] = section.GetJson(); - config = wrapper.toStyledString(); + config[CONFIG_SECTION] = section.GetJson(); } else { LOG(WARNING) << "The Orthanc configuration file doesn't contain a section \"" << CONFIG_SECTION << "\" to configure the Stone Web viewer: " << "Will use default settings"; + + std::string s; Orthanc::EmbeddedResources::GetDirectoryResource( - config, Orthanc::EmbeddedResources::WEB_APPLICATION, "/configuration.json"); + s, Orthanc::EmbeddedResources::WEB_APPLICATION, "/configuration.json"); + + Json::Reader reader; + if (!reader.parse(s, config) || + config.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Cannot read the default configuration"); + } } - OrthancPluginAnswerBuffer(context, output, config.c_str(), config.size(), "application/json"); + config[CONFIG_SECTION]["OrthancApiRoot"] = ".."; + + const std::string s = config.toStyledString(); + OrthancPluginAnswerBuffer(context, output, s.c_str(), s.size(), "application/json"); } } diff -r eb59fbee071e -r 2931f5e15320 Applications/StoneWebViewer/WebApplication/app.js --- a/Applications/StoneWebViewer/WebApplication/app.js Sat Nov 28 16:16:24 2020 +0100 +++ b/Applications/StoneWebViewer/WebApplication/app.js Mon Nov 30 15:36:40 2020 +0100 @@ -27,6 +27,11 @@ var STUDY_INSTANCE_UID = '0020,000d'; var STUDY_DESCRIPTION = '0008,1030'; var STUDY_DATE = '0008,0020'; +var PATIENT_ID = '0010,0020'; +var PATIENT_NAME = '0010,0010'; +var SERIES_NUMBER = '0020,0011'; +var SERIES_DESCRIPTION = '0008,103e'; +var MODALITY = '0008,0060'; // Registry of the PDF series for which the instance metadata is still waiting var pendingSeriesPdf_ = {}; @@ -100,6 +105,21 @@ } +/** + * Enable support for tooltips in Bootstrap. This function must be + * called after each modification to the DOM that introduces new + * tooltips (e.g. after loading studies). + **/ +function RefreshTooltips() +{ + $('[data-toggle="tooltip"]').tooltip({ + placement: 'bottom', + container: 'body', + trigger: 'hover' + }); +} + + Vue.component('viewport', { props: [ 'left', 'top', 'width', 'height', 'canvasId', 'active', 'content', 'viewportIndex', @@ -329,6 +349,8 @@ showReferenceLines: true, synchronizedBrowsing: false, globalConfiguration: {}, + creatingArchive: false, + archiveJob: '', modalWarning: false, modalNotDiagnostic: false, @@ -539,6 +561,10 @@ this.series = series; this.seriesIndex = seriesIndex; this.ready = true; + + Vue.nextTick(function() { + RefreshTooltips(); + }); }, SeriesDragStart: function(event, seriesIndex) { @@ -872,6 +898,63 @@ } this.SetMouseButtonActions(left, middle, right); + }, + + CheckIsDownloadComplete: function() + { + if (this.creatingArchive && + this.archiveJob.length > 0) { + + var that = this; + axios.get(that.globalConfiguration.OrthancApiRoot + '/jobs/' + that.archiveJob) + .then(function(response) { + console.log('Progress of archive job ' + that.archiveJob + ': ' + response.data['Progress'] + '%'); + var state = response.data['State']; + if (state == 'Success') { + that.creatingArchive = false; + window.open(that.globalConfiguration.OrthancApiRoot + '/jobs/' + that.archiveJob + '/archive'); + } + else if (state == 'Running') { + setTimeout(that.CheckIsDownloadComplete, 1000); + } + else { + alert('Error while creating the archive in Orthanc: ' + response.data['ErrorDescription']); + that.creatingArchive = false; + } + }) + .catch(function(error) { + alert('The archive job is not available anymore in Orthanc'); + that.creatingArchive = false; + }); + } + }, + + DownloadStudy: function(studyInstanceUid) + { + console.log('Creating archive for study: ' + studyInstanceUid); + + var that = this; + axios.post(this.globalConfiguration.OrthancApiRoot + '/tools/lookup', studyInstanceUid) + .then(function(response) { + if (response.data.length != 1) { + throw(''); + } + else { + var orthancId = response.data[0]['ID']; + axios.post(that.globalConfiguration.OrthancApiRoot + '/studies/' + orthancId + '/archive', + { + 'Asynchronous' : true + }) + .then(function(response) { + that.creatingArchive = true; + that.archiveJob = response.data.ID; + setTimeout(that.CheckIsDownloadComplete, 1000); + }); + } + }) + .catch(function (error) { + alert('Cannot find the study in Orthanc'); + }); } }, @@ -1013,12 +1096,7 @@ $(document).ready(function() { - // Enable support for tooltips in Bootstrap - $('[data-toggle="tooltip"]').tooltip({ - placement: 'bottom', - container: 'body', - trigger: 'hover' - }); + RefreshTooltips(); //app.modalWarning = true; diff -r eb59fbee071e -r 2931f5e15320 Applications/StoneWebViewer/WebApplication/configuration.json --- a/Applications/StoneWebViewer/WebApplication/configuration.json Sat Nov 28 16:16:24 2020 +0100 +++ b/Applications/StoneWebViewer/WebApplication/configuration.json Mon Nov 30 15:36:40 2020 +0100 @@ -47,7 +47,12 @@ * active viewport as a JPEG file. **/ "DownloadAsJpegEnabled" : true, - + + /** + * Enables/disables the button to download the display study. + **/ + "DownloadStudyEnabled" : true, + /** * The allowed origin for messages corresponding to dynamic actions * triggered by another Web page using "window.postMessage()". The @@ -56,6 +61,13 @@ * set, all the requests for dynamic actions will be rejected. * https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage **/ - "ExpectedMessageOrigin" : "http://localhost:8042" + "ExpectedMessageOrigin" : "http://localhost:8042", + + /** + * The following parameter can be set if running the Stone Web + * viewer from Orthanc, but without using the associated plugin. + * Using the plugin would overwrite this setting. + **/ + "OrthancApiRoot" : "schnol" } } diff -r eb59fbee071e -r 2931f5e15320 Applications/StoneWebViewer/WebApplication/index.html --- a/Applications/StoneWebViewer/WebApplication/index.html Sat Nov 28 16:16:24 2020 +0100 +++ b/Applications/StoneWebViewer/WebApplication/index.html Mon Nov 30 15:36:40 2020 +0100 @@ -142,9 +142,9 @@ v-bind:class="{ active: study.selected }" @click="study.selected = !study.selected"> - {{ study.tags['0008,1030'] }} - - [{{ FormatDate(study.tags['0008,0020']) }}] + {{ study.tags[STUDY_DESCRIPTION] }} + + [{{ FormatDate(study.tags[STUDY_DATE]) }}]   @@ -173,16 +173,21 @@ - {{ study.tags['0008,1030'] }} + {{ study.tags[STUDY_DESCRIPTION] }}
- {{ FormatDate(study.tags['0008,0020']) }} + {{ FormatDate(study.tags[STUDY_DATE]) }}
@@ -191,7 +196,7 @@
  • @@ -214,7 +219,7 @@
    + v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags[MODALITY] + '] ' + series[seriesIndex].tags[SERIES_DESCRIPTION]"> @@ -222,7 +227,7 @@ v-bind:src="series[seriesIndex].thumbnail" style="vertical-align:baseline" width="65px" height="65px" - v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']" + v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags[MODALITY] + '] ' + series[seriesIndex].tags[SERIES_DESCRIPTION]" />

    - [{{ series[seriesIndex].tags['0008,0060'] }}] - {{ series[seriesIndex].tags['0008,103e'] }} + [{{ series[seriesIndex].tags[MODALITY] }}] + {{ series[seriesIndex].tags[SERIES_DESCRIPTION] }}

  • @@ -254,7 +259,7 @@ v-bind:src="sopInstanceUid in multiframeInstanceThumbnails ? multiframeInstanceThumbnails[sopInstanceUid] : series[seriesIndex].thumbnail" style="vertical-align:baseline" width="65px" height="65px" - v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']" + v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags[MODALITY] + '] ' + series[seriesIndex].tags[SERIES_DESCRIPTION]" />
    @@ -267,8 +272,8 @@ v-on:dragstart="MultiframeInstanceDragStart($event, seriesIndex, sopInstanceUid)" v-on:click="MultiframeInstanceDragStart($event, seriesIndex, sopInstanceUid)">

    - [{{ series[seriesIndex].tags['0008,0060'] }}] - {{ series[seriesIndex].tags['0008,103e'] }} + [{{ series[seriesIndex].tags[MODALITY] }}] + {{ series[seriesIndex].tags[SERIES_DESCRIPTION] }}

    @@ -601,13 +606,13 @@
    - {{ content.series.tags['0010,0010'] }}
    - {{ content.series.tags['0010,0020'] }} + {{ content.series.tags[PATIENT_NAME] }}
    + {{ content.series.tags[PATIENT_ID] }}
    - {{ content.series.tags['0008,1030'] }}
    - {{ app.FormatDate(content.series.tags['0008,0020']) }}
    - {{ content.series.tags['0020,0011'] }} | {{ content.series.tags['0008,103e'] }} + {{ content.series.tags[STUDY_DESCRIPTION] }}
    + {{ app.FormatDate(content.series.tags[STUDY_DATE]) }}
    + {{ content.series.tags[SERIES_NUMBER] }} | {{ content.series.tags[SERIES_DESCRIPTION] }}