Mercurial > hg > orthanc-stone
diff Applications/StoneWebViewer/WebApplication/app.js @ 1538:d1806b4e4839
moving OrthancStone/Samples/ as Applications/Samples/
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 11 Aug 2020 13:24:38 +0200 |
parents | StoneWebViewer/WebApplication/app.js@fb74ed5d8c22 |
children | bf195fc0797e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebApplication/app.js Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,614 @@ +/** + * 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/>. + **/ + + +var COLORS = [ 'blue', 'red', 'green', 'yellow', 'violet' ]; +var SERIES_INSTANCE_UID = '0020,000e'; +var STUDY_INSTANCE_UID = '0020,000d'; +var STUDY_DESCRIPTION = '0008,1030'; +var STUDY_DATE = '0008,0020'; + + +function getParameterFromUrl(key) { + var url = window.location.search.substring(1); + var args = url.split('&'); + for (var i = 0; i < args.length; i++) { + var arg = args[i].split('='); + if (arg[0] == key) { + return arg[1]; + } + } +} + + +Vue.component('viewport', { + props: [ 'left', 'top', 'width', 'height', 'canvasId', 'active', 'series', 'viewportIndex', + 'quality', 'framesCount', 'currentFrame', 'showInfo' ], + template: '#viewport-template', + data: function () { + return { + stone: stone, // To access global object "stone" from "index.html" + status: 'waiting' + } + }, + watch: { + series: function(newVal, oldVal) { + this.status = 'loading'; + + var studyInstanceUid = newVal.tags[STUDY_INSTANCE_UID]; + var seriesInstanceUid = newVal.tags[SERIES_INSTANCE_UID]; + stone.SpeedUpFetchSeriesMetadata(studyInstanceUid, seriesInstanceUid); + + if ((newVal.type == stone.ThumbnailType.IMAGE || + newVal.type == stone.ThumbnailType.NO_PREVIEW) && + newVal.complete) { + this.status = 'ready'; + + var that = this; + Vue.nextTick(function() { + stone.LoadSeriesInViewport(that.canvasId, seriesInstanceUid); + }); + } + else if (newVal.type == stone.ThumbnailType.PDF || + newVal.type == stone.ThumbnailType.VIDEO) { + // TODO + } + } + }, + methods: { + SeriesDragAccept: function(event) { + event.preventDefault(); + }, + SeriesDragDrop: function(event) { + event.preventDefault(); + this.$emit('updated-series', event.dataTransfer.getData('seriesIndex')); + }, + MakeActive: function() { + this.$emit('selected-viewport'); + }, + DecrementFrame: function() { + stone.DecrementFrame(this.canvasId); + }, + IncrementFrame: function() { + stone.IncrementFrame(this.canvasId); + } + } +}) + + +var app = new Vue({ + el: '#wv', + data: function() { + return { + stone: stone, // To access global object "stone" from "index.html" + ready: false, + leftMode: 'grid', // Can be 'small', 'grid' or 'full' + leftVisible: true, + showWarning: false, + viewportLayoutButtonsVisible: false, + activeViewport: 0, + showInfo: true, + showReferenceLines: true, + + viewport1Width: '100%', + viewport1Height: '100%', + viewport1Left: '0%', + viewport1Top: '0%', + viewport1Visible: true, + viewport1Series: {}, + viewport1Quality: '', + viewport1FramesCount: 0, + viewport1CurrentFrame: 0, + + viewport2Width: '100%', + viewport2Height: '100%', + viewport2Left: '0%', + viewport2Top: '0%', + viewport2Visible: false, + viewport2Series: {}, + viewport2Quality: '', + viewport2FramesCount: 0, + viewport2CurrentFrame: 0, + + viewport3Width: '100%', + viewport3Height: '100%', + viewport3Left: '0%', + viewport3Top: '0%', + viewport3Visible: false, + viewport3Series: {}, + viewport3Quality: '', + viewport3FramesCount: 0, + viewport3CurrentFrame: 0, + + viewport4Width: '100%', + viewport4Height: '100%', + viewport4Left: '0%', + viewport4Top: '0%', + viewport4Visible: false, + viewport4Series: {}, + viewport4Quality: '', + viewport4FramesCount: 0, + viewport4CurrentFrame: 0, + + series: [], + studies: [], + seriesIndex: {} // Maps "SeriesInstanceUID" to "index in this.series" + } + }, + computed: { + getSelectedStudies() { + var s = ''; + for (var i = 0; i < this.studies.length; i++) { + if (this.studies[i].selected) { + if (s.length > 0) + s += ', '; + s += (this.studies[i].tags[STUDY_DESCRIPTION] + ' [' + + this.studies[i].tags[STUDY_DATE] + ']'); + } + } + if (s.length == 0) + return '...'; + else + return s; + } + }, + watch: { + leftVisible: function(newVal, oldVal) { + this.FitContent(); + }, + showReferenceLines: function(newVal, oldVal) { + stone.ShowReferenceLines(newVal ? 1 : 0); + } + }, + methods: { + FitContent() { + // This function can be used even if WebAssembly is not initialized yet + if (typeof stone._AllViewportsUpdateSize !== 'undefined') { + this.$nextTick(function () { + stone.AllViewportsUpdateSize(true /* fit content */); + }); + } + }, + + GetActiveSeries() { + var s = []; + + if ('tags' in this.viewport1Series) + s.push(this.viewport1Series.tags[SERIES_INSTANCE_UID]); + + if ('tags' in this.viewport2Series) + s.push(this.viewport2Series.tags[SERIES_INSTANCE_UID]); + + if ('tags' in this.viewport3Series) + s.push(this.viewport3Series.tags[SERIES_INSTANCE_UID]); + + if ('tags' in this.viewport4Series) + s.push(this.viewport4Series.tags[SERIES_INSTANCE_UID]); + + return s; + }, + + GetActiveCanvas() { + if (this.activeViewport == 1) { + return 'canvas1'; + } + else if (this.activeViewport == 2) { + return 'canvas2'; + } + else if (this.activeViewport == 3) { + return 'canvas3'; + } + else if (this.activeViewport == 4) { + return 'canvas4'; + } + else { + return 'canvas1'; + } + }, + + SetResources: function(sourceStudies, sourceSeries) { + var indexStudies = {}; + + var studies = []; + var posColor = 0; + + for (var i = 0; i < sourceStudies.length; i++) { + var studyInstanceUid = sourceStudies[i][STUDY_INSTANCE_UID]; + if (studyInstanceUid !== undefined) { + if (studyInstanceUid in indexStudies) { + console.error('Twice the same study: ' + studyInstanceUid); + } else { + indexStudies[studyInstanceUid] = studies.length; + + studies.push({ + 'studyInstanceUid' : studyInstanceUid, + 'series' : [ ], + 'color' : COLORS[posColor], + 'selected' : true, + 'tags' : sourceStudies[i] + }); + + posColor = (posColor + 1) % COLORS.length; + } + } + } + + var series = []; + var seriesIndex = {}; + + for (var i = 0; i < sourceSeries.length; i++) { + var studyInstanceUid = sourceSeries[i][STUDY_INSTANCE_UID]; + var seriesInstanceUid = sourceSeries[i][SERIES_INSTANCE_UID]; + if (studyInstanceUid !== undefined && + seriesInstanceUid !== undefined) { + if (studyInstanceUid in indexStudies) { + seriesIndex[seriesInstanceUid] = series.length; + var study = studies[indexStudies[studyInstanceUid]]; + study.series.push(i); + series.push({ + //'length' : 4, + 'complete' : false, + 'type' : stone.ThumbnailType.LOADING, + 'color': study.color, + 'tags': sourceSeries[i] + }); + } + } + } + + this.studies = studies; + this.series = series; + this.seriesIndex = seriesIndex; + this.ready = true; + }, + + SeriesDragStart: function(event, seriesIndex) { + event.dataTransfer.setData('seriesIndex', seriesIndex); + }, + + SetViewportSeries: function(viewportIndex, seriesIndex) { + var series = this.series[seriesIndex]; + + if (viewportIndex == 1) { + this.viewport1Series = series; + } + else if (viewportIndex == 2) { + this.viewport2Series = series; + } + else if (viewportIndex == 3) { + this.viewport3Series = series; + } + else if (viewportIndex == 4) { + this.viewport4Series = series; + } + }, + + ClickSeries: function(seriesIndex) { + this.SetViewportSeries(this.activeViewport, seriesIndex); + }, + + HideViewport: function(index) { + if (index == 1) { + this.viewport1Visible = false; + } + else if (index == 2) { + this.viewport2Visible = false; + } + else if (index == 3) { + this.viewport3Visible = false; + } + else if (index == 4) { + this.viewport4Visible = false; + } + }, + + ShowViewport: function(index, left, top, width, height) { + if (index == 1) { + this.viewport1Visible = true; + this.viewport1Left = left; + this.viewport1Top = top; + this.viewport1Width = width; + this.viewport1Height = height; + } + else if (index == 2) { + this.viewport2Visible = true; + this.viewport2Left = left; + this.viewport2Top = top; + this.viewport2Width = width; + this.viewport2Height = height; + } + else if (index == 3) { + this.viewport3Visible = true; + this.viewport3Left = left; + this.viewport3Top = top; + this.viewport3Width = width; + this.viewport3Height = height; + } + else if (index == 4) { + this.viewport4Visible = true; + this.viewport4Left = left; + this.viewport4Top = top; + this.viewport4Width = width; + this.viewport4Height = height; + } + }, + + SetViewportLayout: function(layout) { + this.viewportLayoutButtonsVisible = false; + if (layout == '1x1') { + this.ShowViewport(1, '0%', '0%', '100%', '100%'); + this.HideViewport(2); + this.HideViewport(3); + this.HideViewport(4); + } + else if (layout == '2x2') { + this.ShowViewport(1, '0%', '0%', '50%', '50%'); + this.ShowViewport(2, '50%', '0%', '50%', '50%'); + this.ShowViewport(3, '0%', '50%', '50%', '50%'); + this.ShowViewport(4, '50%', '50%', '50%', '50%'); + } + else if (layout == '2x1') { + this.ShowViewport(1, '0%', '0%', '50%', '100%'); + this.ShowViewport(2, '50%', '0%', '50%', '100%'); + this.HideViewport(3); + this.HideViewport(4); + } + else if (layout == '1x2') { + this.ShowViewport(1, '0%', '0%', '100%', '50%'); + this.ShowViewport(2, '0%', '50%', '100%', '50%'); + this.HideViewport(3); + this.HideViewport(4); + } + + this.FitContent(); + }, + + UpdateSeriesThumbnail: function(seriesInstanceUid) { + if (seriesInstanceUid in this.seriesIndex) { + var index = this.seriesIndex[seriesInstanceUid]; + var series = this.series[index]; + + var type = stone.LoadSeriesThumbnail(seriesInstanceUid); + series.type = type; + + if (type == stone.ThumbnailType.IMAGE) { + series.thumbnail = stone.GetStringBuffer(); + } + + // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating + this.$set(this.series, index, series); + } + }, + + UpdateIsSeriesComplete: function(seriesInstanceUid) { + if (seriesInstanceUid in this.seriesIndex) { + var index = this.seriesIndex[seriesInstanceUid]; + var series = this.series[index]; + + series.complete = stone.IsSeriesComplete(seriesInstanceUid); + + // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating + this.$set(this.series, index, series); + + if ('tags' in this.viewport1Series && + this.viewport1Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { + this.$set(this.viewport1Series, series); + } + + if ('tags' in this.viewport2Series && + this.viewport2Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { + this.$set(this.viewport2Series, series); + } + + if ('tags' in this.viewport3Series && + this.viewport3Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { + this.$set(this.viewport3Series, series); + } + + if ('tags' in this.viewport4Series && + this.viewport4Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { + this.$set(this.viewport4Series, series); + } + } + }, + + SetWindowing(center, width) { + var canvas = this.GetActiveCanvas(); + if (canvas != '') { + stone.SetWindowing(canvas, center, width); + } + }, + + SetDefaultWindowing() { + var canvas = this.GetActiveCanvas(); + if (canvas != '') { + stone.SetDefaultWindowing(canvas); + } + }, + + InvertContrast() { + var canvas = this.GetActiveCanvas(); + if (canvas != '') { + stone.InvertContrast(canvas); + } + } + }, + + mounted: function() { + this.SetViewportLayout('1x1'); + } +}); + + + +window.addEventListener('StoneInitialized', function() { + stone.Setup(Module); + stone.SetOrthancRoot('..', true); + console.warn('Native Stone properly intialized'); + + var study = getParameterFromUrl('study'); + var series = getParameterFromUrl('series'); + + if (study === undefined) { + alert('No study was provided in the URL!'); + } else { + if (series === undefined) { + console.warn('Loading study: ' + study); + stone.FetchStudy(study); + } else { + console.warn('Loading series: ' + series + ' from study ' + study); + stone.FetchSeries(study, series); + app.leftMode = 'full'; + } + } +}); + + +window.addEventListener('ResourcesLoaded', function() { + console.log('resources loaded'); + + var studies = []; + for (var i = 0; i < stone.GetStudiesCount(); i++) { + stone.LoadStudyTags(i); + studies.push(JSON.parse(stone.GetStringBuffer())); + } + + var series = []; + for (var i = 0; i < stone.GetSeriesCount(); i++) { + stone.LoadSeriesTags(i); + series.push(JSON.parse(stone.GetStringBuffer())); + } + + app.SetResources(studies, series); + + for (var i = 0; i < app.series.length; i++) { + var seriesInstanceUid = app.series[i].tags[SERIES_INSTANCE_UID]; + app.UpdateSeriesThumbnail(seriesInstanceUid); + app.UpdateIsSeriesComplete(seriesInstanceUid); + } +}); + + +window.addEventListener('ThumbnailLoaded', function(args) { + //var studyInstanceUid = args.detail.studyInstanceUid; + var seriesInstanceUid = args.detail.seriesInstanceUid; + app.UpdateSeriesThumbnail(seriesInstanceUid); +}); + + +window.addEventListener('MetadataLoaded', function(args) { + //var studyInstanceUid = args.detail.studyInstanceUid; + var seriesInstanceUid = args.detail.seriesInstanceUid; + app.UpdateIsSeriesComplete(seriesInstanceUid); +}); + + +window.addEventListener('FrameUpdated', function(args) { + var canvasId = args.detail.canvasId; + var framesCount = args.detail.framesCount; + var currentFrame = (args.detail.currentFrame + 1); + var quality = args.detail.quality; + + if (canvasId == 'canvas1') { + app.viewport1CurrentFrame = currentFrame; + app.viewport1FramesCount = framesCount; + app.viewport1Quality = quality; + } + else if (canvasId == 'canvas2') { + app.viewport2CurrentFrame = currentFrame; + app.viewport2FramesCount = framesCount; + app.viewport2Quality = quality; + } + else if (canvasId == 'canvas3') { + app.viewport3CurrentFrame = currentFrame; + app.viewport3FramesCount = framesCount; + app.viewport3Quality = quality; + } + else if (canvasId == 'canvas4') { + app.viewport4CurrentFrame = currentFrame; + app.viewport4FramesCount = framesCount; + app.viewport4Quality = quality; + } +}); + + +window.addEventListener('StoneException', function() { + console.error('Exception catched in Stone'); +}); + + + + + + +$(document).ready(function() { + // Enable support for tooltips in Bootstrap + //$('[data-toggle="tooltip"]').tooltip(); + + //app.showWarning = true; + + + $('#windowing-popover').popover({ + container: 'body', + content: $('#windowing-content').html(), + template: '<div class="popover wvToolbar__windowingPresetConfigPopover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>', + placement: 'auto', + html: true, + sanitize: false, + trigger: 'focus' // Close on click + }); + + + var wasmSource = 'StoneWebViewer.js'; + + // Option 1: Loading script using plain HTML + + /* + var script = document.createElement('script'); + script.src = wasmSource; + script.type = 'text/javascript'; + document.body.appendChild(script); + */ + + // Option 2: Loading script using AJAX (gives the opportunity to + // report explicit errors) + + axios.get(wasmSource) + .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'); + }); +}); + + +// "Prevent Bootstrap dropdown from closing on clicks" for the list of +// studies: https://stackoverflow.com/questions/26639346 +$('.dropdown-menu').click(function(e) { + e.stopPropagation(); +}); + + +// Disable the selection of text using the mouse +document.onselectstart = new Function ('return false');