comparison 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
comparison
equal deleted inserted replaced
1537:de8cf5859e84 1538:d1806b4e4839
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20
21
22 var COLORS = [ 'blue', 'red', 'green', 'yellow', 'violet' ];
23 var SERIES_INSTANCE_UID = '0020,000e';
24 var STUDY_INSTANCE_UID = '0020,000d';
25 var STUDY_DESCRIPTION = '0008,1030';
26 var STUDY_DATE = '0008,0020';
27
28
29 function getParameterFromUrl(key) {
30 var url = window.location.search.substring(1);
31 var args = url.split('&');
32 for (var i = 0; i < args.length; i++) {
33 var arg = args[i].split('=');
34 if (arg[0] == key) {
35 return arg[1];
36 }
37 }
38 }
39
40
41 Vue.component('viewport', {
42 props: [ 'left', 'top', 'width', 'height', 'canvasId', 'active', 'series', 'viewportIndex',
43 'quality', 'framesCount', 'currentFrame', 'showInfo' ],
44 template: '#viewport-template',
45 data: function () {
46 return {
47 stone: stone, // To access global object "stone" from "index.html"
48 status: 'waiting'
49 }
50 },
51 watch: {
52 series: function(newVal, oldVal) {
53 this.status = 'loading';
54
55 var studyInstanceUid = newVal.tags[STUDY_INSTANCE_UID];
56 var seriesInstanceUid = newVal.tags[SERIES_INSTANCE_UID];
57 stone.SpeedUpFetchSeriesMetadata(studyInstanceUid, seriesInstanceUid);
58
59 if ((newVal.type == stone.ThumbnailType.IMAGE ||
60 newVal.type == stone.ThumbnailType.NO_PREVIEW) &&
61 newVal.complete) {
62 this.status = 'ready';
63
64 var that = this;
65 Vue.nextTick(function() {
66 stone.LoadSeriesInViewport(that.canvasId, seriesInstanceUid);
67 });
68 }
69 else if (newVal.type == stone.ThumbnailType.PDF ||
70 newVal.type == stone.ThumbnailType.VIDEO) {
71 // TODO
72 }
73 }
74 },
75 methods: {
76 SeriesDragAccept: function(event) {
77 event.preventDefault();
78 },
79 SeriesDragDrop: function(event) {
80 event.preventDefault();
81 this.$emit('updated-series', event.dataTransfer.getData('seriesIndex'));
82 },
83 MakeActive: function() {
84 this.$emit('selected-viewport');
85 },
86 DecrementFrame: function() {
87 stone.DecrementFrame(this.canvasId);
88 },
89 IncrementFrame: function() {
90 stone.IncrementFrame(this.canvasId);
91 }
92 }
93 })
94
95
96 var app = new Vue({
97 el: '#wv',
98 data: function() {
99 return {
100 stone: stone, // To access global object "stone" from "index.html"
101 ready: false,
102 leftMode: 'grid', // Can be 'small', 'grid' or 'full'
103 leftVisible: true,
104 showWarning: false,
105 viewportLayoutButtonsVisible: false,
106 activeViewport: 0,
107 showInfo: true,
108 showReferenceLines: true,
109
110 viewport1Width: '100%',
111 viewport1Height: '100%',
112 viewport1Left: '0%',
113 viewport1Top: '0%',
114 viewport1Visible: true,
115 viewport1Series: {},
116 viewport1Quality: '',
117 viewport1FramesCount: 0,
118 viewport1CurrentFrame: 0,
119
120 viewport2Width: '100%',
121 viewport2Height: '100%',
122 viewport2Left: '0%',
123 viewport2Top: '0%',
124 viewport2Visible: false,
125 viewport2Series: {},
126 viewport2Quality: '',
127 viewport2FramesCount: 0,
128 viewport2CurrentFrame: 0,
129
130 viewport3Width: '100%',
131 viewport3Height: '100%',
132 viewport3Left: '0%',
133 viewport3Top: '0%',
134 viewport3Visible: false,
135 viewport3Series: {},
136 viewport3Quality: '',
137 viewport3FramesCount: 0,
138 viewport3CurrentFrame: 0,
139
140 viewport4Width: '100%',
141 viewport4Height: '100%',
142 viewport4Left: '0%',
143 viewport4Top: '0%',
144 viewport4Visible: false,
145 viewport4Series: {},
146 viewport4Quality: '',
147 viewport4FramesCount: 0,
148 viewport4CurrentFrame: 0,
149
150 series: [],
151 studies: [],
152 seriesIndex: {} // Maps "SeriesInstanceUID" to "index in this.series"
153 }
154 },
155 computed: {
156 getSelectedStudies() {
157 var s = '';
158 for (var i = 0; i < this.studies.length; i++) {
159 if (this.studies[i].selected) {
160 if (s.length > 0)
161 s += ', ';
162 s += (this.studies[i].tags[STUDY_DESCRIPTION] + ' [' +
163 this.studies[i].tags[STUDY_DATE] + ']');
164 }
165 }
166 if (s.length == 0)
167 return '...';
168 else
169 return s;
170 }
171 },
172 watch: {
173 leftVisible: function(newVal, oldVal) {
174 this.FitContent();
175 },
176 showReferenceLines: function(newVal, oldVal) {
177 stone.ShowReferenceLines(newVal ? 1 : 0);
178 }
179 },
180 methods: {
181 FitContent() {
182 // This function can be used even if WebAssembly is not initialized yet
183 if (typeof stone._AllViewportsUpdateSize !== 'undefined') {
184 this.$nextTick(function () {
185 stone.AllViewportsUpdateSize(true /* fit content */);
186 });
187 }
188 },
189
190 GetActiveSeries() {
191 var s = [];
192
193 if ('tags' in this.viewport1Series)
194 s.push(this.viewport1Series.tags[SERIES_INSTANCE_UID]);
195
196 if ('tags' in this.viewport2Series)
197 s.push(this.viewport2Series.tags[SERIES_INSTANCE_UID]);
198
199 if ('tags' in this.viewport3Series)
200 s.push(this.viewport3Series.tags[SERIES_INSTANCE_UID]);
201
202 if ('tags' in this.viewport4Series)
203 s.push(this.viewport4Series.tags[SERIES_INSTANCE_UID]);
204
205 return s;
206 },
207
208 GetActiveCanvas() {
209 if (this.activeViewport == 1) {
210 return 'canvas1';
211 }
212 else if (this.activeViewport == 2) {
213 return 'canvas2';
214 }
215 else if (this.activeViewport == 3) {
216 return 'canvas3';
217 }
218 else if (this.activeViewport == 4) {
219 return 'canvas4';
220 }
221 else {
222 return 'canvas1';
223 }
224 },
225
226 SetResources: function(sourceStudies, sourceSeries) {
227 var indexStudies = {};
228
229 var studies = [];
230 var posColor = 0;
231
232 for (var i = 0; i < sourceStudies.length; i++) {
233 var studyInstanceUid = sourceStudies[i][STUDY_INSTANCE_UID];
234 if (studyInstanceUid !== undefined) {
235 if (studyInstanceUid in indexStudies) {
236 console.error('Twice the same study: ' + studyInstanceUid);
237 } else {
238 indexStudies[studyInstanceUid] = studies.length;
239
240 studies.push({
241 'studyInstanceUid' : studyInstanceUid,
242 'series' : [ ],
243 'color' : COLORS[posColor],
244 'selected' : true,
245 'tags' : sourceStudies[i]
246 });
247
248 posColor = (posColor + 1) % COLORS.length;
249 }
250 }
251 }
252
253 var series = [];
254 var seriesIndex = {};
255
256 for (var i = 0; i < sourceSeries.length; i++) {
257 var studyInstanceUid = sourceSeries[i][STUDY_INSTANCE_UID];
258 var seriesInstanceUid = sourceSeries[i][SERIES_INSTANCE_UID];
259 if (studyInstanceUid !== undefined &&
260 seriesInstanceUid !== undefined) {
261 if (studyInstanceUid in indexStudies) {
262 seriesIndex[seriesInstanceUid] = series.length;
263 var study = studies[indexStudies[studyInstanceUid]];
264 study.series.push(i);
265 series.push({
266 //'length' : 4,
267 'complete' : false,
268 'type' : stone.ThumbnailType.LOADING,
269 'color': study.color,
270 'tags': sourceSeries[i]
271 });
272 }
273 }
274 }
275
276 this.studies = studies;
277 this.series = series;
278 this.seriesIndex = seriesIndex;
279 this.ready = true;
280 },
281
282 SeriesDragStart: function(event, seriesIndex) {
283 event.dataTransfer.setData('seriesIndex', seriesIndex);
284 },
285
286 SetViewportSeries: function(viewportIndex, seriesIndex) {
287 var series = this.series[seriesIndex];
288
289 if (viewportIndex == 1) {
290 this.viewport1Series = series;
291 }
292 else if (viewportIndex == 2) {
293 this.viewport2Series = series;
294 }
295 else if (viewportIndex == 3) {
296 this.viewport3Series = series;
297 }
298 else if (viewportIndex == 4) {
299 this.viewport4Series = series;
300 }
301 },
302
303 ClickSeries: function(seriesIndex) {
304 this.SetViewportSeries(this.activeViewport, seriesIndex);
305 },
306
307 HideViewport: function(index) {
308 if (index == 1) {
309 this.viewport1Visible = false;
310 }
311 else if (index == 2) {
312 this.viewport2Visible = false;
313 }
314 else if (index == 3) {
315 this.viewport3Visible = false;
316 }
317 else if (index == 4) {
318 this.viewport4Visible = false;
319 }
320 },
321
322 ShowViewport: function(index, left, top, width, height) {
323 if (index == 1) {
324 this.viewport1Visible = true;
325 this.viewport1Left = left;
326 this.viewport1Top = top;
327 this.viewport1Width = width;
328 this.viewport1Height = height;
329 }
330 else if (index == 2) {
331 this.viewport2Visible = true;
332 this.viewport2Left = left;
333 this.viewport2Top = top;
334 this.viewport2Width = width;
335 this.viewport2Height = height;
336 }
337 else if (index == 3) {
338 this.viewport3Visible = true;
339 this.viewport3Left = left;
340 this.viewport3Top = top;
341 this.viewport3Width = width;
342 this.viewport3Height = height;
343 }
344 else if (index == 4) {
345 this.viewport4Visible = true;
346 this.viewport4Left = left;
347 this.viewport4Top = top;
348 this.viewport4Width = width;
349 this.viewport4Height = height;
350 }
351 },
352
353 SetViewportLayout: function(layout) {
354 this.viewportLayoutButtonsVisible = false;
355 if (layout == '1x1') {
356 this.ShowViewport(1, '0%', '0%', '100%', '100%');
357 this.HideViewport(2);
358 this.HideViewport(3);
359 this.HideViewport(4);
360 }
361 else if (layout == '2x2') {
362 this.ShowViewport(1, '0%', '0%', '50%', '50%');
363 this.ShowViewport(2, '50%', '0%', '50%', '50%');
364 this.ShowViewport(3, '0%', '50%', '50%', '50%');
365 this.ShowViewport(4, '50%', '50%', '50%', '50%');
366 }
367 else if (layout == '2x1') {
368 this.ShowViewport(1, '0%', '0%', '50%', '100%');
369 this.ShowViewport(2, '50%', '0%', '50%', '100%');
370 this.HideViewport(3);
371 this.HideViewport(4);
372 }
373 else if (layout == '1x2') {
374 this.ShowViewport(1, '0%', '0%', '100%', '50%');
375 this.ShowViewport(2, '0%', '50%', '100%', '50%');
376 this.HideViewport(3);
377 this.HideViewport(4);
378 }
379
380 this.FitContent();
381 },
382
383 UpdateSeriesThumbnail: function(seriesInstanceUid) {
384 if (seriesInstanceUid in this.seriesIndex) {
385 var index = this.seriesIndex[seriesInstanceUid];
386 var series = this.series[index];
387
388 var type = stone.LoadSeriesThumbnail(seriesInstanceUid);
389 series.type = type;
390
391 if (type == stone.ThumbnailType.IMAGE) {
392 series.thumbnail = stone.GetStringBuffer();
393 }
394
395 // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
396 this.$set(this.series, index, series);
397 }
398 },
399
400 UpdateIsSeriesComplete: function(seriesInstanceUid) {
401 if (seriesInstanceUid in this.seriesIndex) {
402 var index = this.seriesIndex[seriesInstanceUid];
403 var series = this.series[index];
404
405 series.complete = stone.IsSeriesComplete(seriesInstanceUid);
406
407 // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
408 this.$set(this.series, index, series);
409
410 if ('tags' in this.viewport1Series &&
411 this.viewport1Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
412 this.$set(this.viewport1Series, series);
413 }
414
415 if ('tags' in this.viewport2Series &&
416 this.viewport2Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
417 this.$set(this.viewport2Series, series);
418 }
419
420 if ('tags' in this.viewport3Series &&
421 this.viewport3Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
422 this.$set(this.viewport3Series, series);
423 }
424
425 if ('tags' in this.viewport4Series &&
426 this.viewport4Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) {
427 this.$set(this.viewport4Series, series);
428 }
429 }
430 },
431
432 SetWindowing(center, width) {
433 var canvas = this.GetActiveCanvas();
434 if (canvas != '') {
435 stone.SetWindowing(canvas, center, width);
436 }
437 },
438
439 SetDefaultWindowing() {
440 var canvas = this.GetActiveCanvas();
441 if (canvas != '') {
442 stone.SetDefaultWindowing(canvas);
443 }
444 },
445
446 InvertContrast() {
447 var canvas = this.GetActiveCanvas();
448 if (canvas != '') {
449 stone.InvertContrast(canvas);
450 }
451 }
452 },
453
454 mounted: function() {
455 this.SetViewportLayout('1x1');
456 }
457 });
458
459
460
461 window.addEventListener('StoneInitialized', function() {
462 stone.Setup(Module);
463 stone.SetOrthancRoot('..', true);
464 console.warn('Native Stone properly intialized');
465
466 var study = getParameterFromUrl('study');
467 var series = getParameterFromUrl('series');
468
469 if (study === undefined) {
470 alert('No study was provided in the URL!');
471 } else {
472 if (series === undefined) {
473 console.warn('Loading study: ' + study);
474 stone.FetchStudy(study);
475 } else {
476 console.warn('Loading series: ' + series + ' from study ' + study);
477 stone.FetchSeries(study, series);
478 app.leftMode = 'full';
479 }
480 }
481 });
482
483
484 window.addEventListener('ResourcesLoaded', function() {
485 console.log('resources loaded');
486
487 var studies = [];
488 for (var i = 0; i < stone.GetStudiesCount(); i++) {
489 stone.LoadStudyTags(i);
490 studies.push(JSON.parse(stone.GetStringBuffer()));
491 }
492
493 var series = [];
494 for (var i = 0; i < stone.GetSeriesCount(); i++) {
495 stone.LoadSeriesTags(i);
496 series.push(JSON.parse(stone.GetStringBuffer()));
497 }
498
499 app.SetResources(studies, series);
500
501 for (var i = 0; i < app.series.length; i++) {
502 var seriesInstanceUid = app.series[i].tags[SERIES_INSTANCE_UID];
503 app.UpdateSeriesThumbnail(seriesInstanceUid);
504 app.UpdateIsSeriesComplete(seriesInstanceUid);
505 }
506 });
507
508
509 window.addEventListener('ThumbnailLoaded', function(args) {
510 //var studyInstanceUid = args.detail.studyInstanceUid;
511 var seriesInstanceUid = args.detail.seriesInstanceUid;
512 app.UpdateSeriesThumbnail(seriesInstanceUid);
513 });
514
515
516 window.addEventListener('MetadataLoaded', function(args) {
517 //var studyInstanceUid = args.detail.studyInstanceUid;
518 var seriesInstanceUid = args.detail.seriesInstanceUid;
519 app.UpdateIsSeriesComplete(seriesInstanceUid);
520 });
521
522
523 window.addEventListener('FrameUpdated', function(args) {
524 var canvasId = args.detail.canvasId;
525 var framesCount = args.detail.framesCount;
526 var currentFrame = (args.detail.currentFrame + 1);
527 var quality = args.detail.quality;
528
529 if (canvasId == 'canvas1') {
530 app.viewport1CurrentFrame = currentFrame;
531 app.viewport1FramesCount = framesCount;
532 app.viewport1Quality = quality;
533 }
534 else if (canvasId == 'canvas2') {
535 app.viewport2CurrentFrame = currentFrame;
536 app.viewport2FramesCount = framesCount;
537 app.viewport2Quality = quality;
538 }
539 else if (canvasId == 'canvas3') {
540 app.viewport3CurrentFrame = currentFrame;
541 app.viewport3FramesCount = framesCount;
542 app.viewport3Quality = quality;
543 }
544 else if (canvasId == 'canvas4') {
545 app.viewport4CurrentFrame = currentFrame;
546 app.viewport4FramesCount = framesCount;
547 app.viewport4Quality = quality;
548 }
549 });
550
551
552 window.addEventListener('StoneException', function() {
553 console.error('Exception catched in Stone');
554 });
555
556
557
558
559
560
561 $(document).ready(function() {
562 // Enable support for tooltips in Bootstrap
563 //$('[data-toggle="tooltip"]').tooltip();
564
565 //app.showWarning = true;
566
567
568 $('#windowing-popover').popover({
569 container: 'body',
570 content: $('#windowing-content').html(),
571 template: '<div class="popover wvToolbar__windowingPresetConfigPopover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
572 placement: 'auto',
573 html: true,
574 sanitize: false,
575 trigger: 'focus' // Close on click
576 });
577
578
579 var wasmSource = 'StoneWebViewer.js';
580
581 // Option 1: Loading script using plain HTML
582
583 /*
584 var script = document.createElement('script');
585 script.src = wasmSource;
586 script.type = 'text/javascript';
587 document.body.appendChild(script);
588 */
589
590 // Option 2: Loading script using AJAX (gives the opportunity to
591 // report explicit errors)
592
593 axios.get(wasmSource)
594 .then(function (response) {
595 var script = document.createElement('script');
596 script.innerHTML = response.data;
597 script.type = 'text/javascript';
598 document.body.appendChild(script);
599 })
600 .catch(function (error) {
601 alert('Cannot load the WebAssembly framework');
602 });
603 });
604
605
606 // "Prevent Bootstrap dropdown from closing on clicks" for the list of
607 // studies: https://stackoverflow.com/questions/26639346
608 $('.dropdown-menu').click(function(e) {
609 e.stopPropagation();
610 });
611
612
613 // Disable the selection of text using the mouse
614 document.onselectstart = new Function ('return false');